字符串比较函数strcmp和strncmp以及memcmp详解与对比分析
字符串比较函数strcmp和strncmp详解
- C语言字符串与内存比较函数详解:strcmp、strncmp与memcmp对比分析
- 使用场景指南
- 性能与安全注意事项
- 返回值陷阱
- 跨平台风险
- 安全比较实践
- 总结选择策略
- 不要用memcmp比较结构体
- C++关于使用memcmp判断结构体是否相等的隐患问题
- Chapter1 字符串比较函数strcmp和strncmp详解
- 一、函数简介
- 二、函数原型与参数解析
- 2.1 strcmp () 原型
- 2.2 strncmp () 原型
- 三、函数实现逻辑
- 3.1 strcmp () 伪代码实现
- 3.2 strncmp () 伪代码实现
- 四、典型使用场景:何时用 strcmp (),何时用 strncmp ()
- 4.1 strcmp () 的适用场景
- 4.2 strncmp () 的适用场景
- 五、关键注意事项
- 5.1 strcmp () 必须依赖 '\0',否则越界
- 5.2 strncmp () 的 n 是无符号整数,不可传负数
- 5.3 返回值不是只有 - 1、0、1
- 5.4 不可比较多字节字符串(如中文)
- 5.5 避免传入 NULL 指针
- 六、函数差异对比:一张表看懂核心区别
- 七、完整示例代码:综合场景实践
C语言字符串与内存比较函数详解:strcmp、strncmp与memcmp对比分析
原文链接:https://blog.csdn.net/m0_37989557/article/details/149144189
在 C 语言开发中,字符串比较是高频操作 —— 从用户名密码验证、配置项匹配,到数据排序与查找,都离不开字符串比较函数。C 标准库<string.h>提供的strcmp()与strncmp()是最常用的两个工具,但开发者常因混淆两者的适用场景、忽略边界条件导致 bug(如缓冲区越界、比较结果异常)。
使用场景指南
优先使用 strcmp:
-
比较用户输入的字符串命令
-
排序纯文本数据
if (strcmp(input, "quit") == 0) exit(0);
优先使用 strncmp:
-
检查协议前缀(如 HTTP/FTP)
-
比较定长字符串标识符
// 检测HTTP请求
if (strncmp(request, "GET ", 4) == 0) {...}
优先使用 memcmp:
-
验证数据结构完整性
-
比较加密哈希值
-
处理二进制文件格式
// 校验数据副本
memcmp(original, backup, sizeof(DataStruct));
性能与安全注意事项
返回值陷阱
// 错误用法:假设返回1/-1
if (strcmp(a, b) == 1) // 不可靠!可能返回任意正数// 正确用法
if (strcmp(a, b) > 0) // 检查符号而非具体值
跨平台风险
// 结构体比较可能因填充字节失败
struct Data { char c; int i; }; // 可能有3字节填充
memcmp(&d1, &d2, sizeof(struct Data)); // 结果可能不一致// 不同平台 内存填充布局和填充数量可能不一致;这样相同代码运行在不同平台,得出的结果可能不一致
安全比较实践
// 敏感数据比较(防时序攻击)
// 以下代码可避免。不做详细分析
int secure_compare(const void *a, const void *b, size_t len) {const uint8_t *pa = a;const uint8_t *pb = b;uint8_t diff = 0;for (size_t i = 0; i < len; i++) {diff |= pa[i] ^ pb[i];}return diff;
}// 时序攻击是一种侧信道攻击(Side-channel attack),
// 攻击者通过测量比较操作的执行时间差异来推断敏感数据的内容。在安全系统中,这种攻击尤其危险:
// 利用的是memcmp函数的漏洞,
// 漏洞原理:
// 比较不同位置所需时间不同:
// 第一个字节不同 → 快速返回
// 最后一个字节不同 → 慢速返回
// 攻击者可测量响应时间模式:逐个分析确定敏感数据,如密码
总结选择策略
不要用memcmp比较结构体
原文链接
除非在项目中可以保证所有的结构体都会使用memset来进行初始化(这个是很难保证的) , 否则就不要直接使用memcmp来比较结构体。
C++关于使用memcmp判断结构体是否相等的隐患问题
原文链接
结构体struct
C++除了除了比较高级的class之外,还有跟他很像的struct,但是如何比较两个结构体是否相等呢(结构体的每个变量都相等),这时候很容易想到了c里面的memcmp函数。
经检验,在使用memcmp比较结构体的时候,有个问题:有时候明明两个结构体的所有变量都是一样的,返回的还是非0。
找到原因
找了很多博客之后,都说和字节对齐有关,之前看过字节对齐,还是不懂为什么。
后来才知道,字节对齐的机制会给结构体填充一些不用的空间,这些空间的内容是随机的,也就是说,如果结构体里面有字节对齐填充的操作了,那么memcmp就不能达到效果。
Chapter1 字符串比较函数strcmp和strncmp详解
原文链接:https://blog.csdn.net/weixin_37800531/article/details/141977891
一、函数简介
字符串比较的本质是按 ASCII 码值逐字符对比,而非比较字符串长度。C 语言中字符串以’\0’作为结束标志,这一特性直接决定了strcmp()与strncmp()的设计逻辑差异:
简单来说:strcmp()是 “全自动” 比较(直到结束符),strncmp()是 “半自动” 比较(指定最大长度)。两者的返回值逻辑一致 —— 均通过字符 ASCII 差值判断大小,但适用场景和安全性差异显著。
二、函数原型与参数解析
要正确使用函数,首先需理解其原型定义(来自<string.h>头文件),尤其是参数的约束与含义。
2.1 strcmp () 原型
int strcmp(const char *str1, const char *str2);
参数说明:
str1/str2:待比较的两个字符串指针(const修饰表示函数不会修改输入字符串,避免误操作);
要求:两个字符串必须以’\0’结尾(否则会触发越界)。
返回值:int 类型,代表比较结果:
返回0:str1与str2内容完全相同;
返回正数:str1中第一个不同字符的 ASCII 值 > str2对应字符;
返回负数:str1中第一个不同字符的 ASCII 值 < str2对应字符。
2.2 strncmp () 原型
int strncmp(const char *str1, const char *str2, size_t n);
参数说明:
前两个参数与strcmp()一致;
n:最大比较字符数(size_t是无符号整数类型,取值≥0);
优势:无需强制要求字符串以’\0’结尾(若n小于字符串长度,会提前终止)。
返回值:逻辑与strcmp()完全一致,仅比较范围受n限制:
若前n个字符完全相同,无论后续内容如何,均返回0;
若未比较到n个字符时已出现不同,返回第一个不同字符的 ASCII 差值。
三、函数实现逻辑
理解函数的实现逻辑,能帮你规避 90% 的使用错误。以下伪代码基于 C 标准(ISO/IEC 9899)的规范,还原了两个函数的核心执行流程(非编译器真实实现,但逻辑一致)。
3.1 strcmp () 伪代码实现
// 功能:比较str1和str2,直到'\0'或不同字符
function strcmp(const char* str1, const char* str2) -> int:// 1. 校验指针合法性(真实库函数可能不校验,直接触发段错误)if str1 == NULL or str2 == NULL:触发未定义行为(如程序崩溃)// 2. 逐字符比较(直到'\0'或不同)while *str1 != '\0' and *str2 != '\0':if *str1 != *str2:// 返回当前字符ASCII差值(str1 - str2)return (unsigned char)*str1 - (unsigned char)*str2// 指针向后移动str1 = str1 + 1str2 = str2 + 1// 3. 处理一方已到'\0'的情况(如"abc" vs "abcd")return (unsigned char)*str1 - (unsigned char)*str2
- 用unsigned char转换字符:避免 ASCII 码中负数(如扩展 ASCII 的 0x80-0xFF)导致的比较错误;
- 终止条件:必须同时满足 “字符相同” 且 “未到 ‘\0’”,否则退出循环;
- 最终差值:若一方先到 ‘\0’(如"a" vs “aa”),则*str1为 ‘\0’(ASCII 0),*str2为 ‘a’(ASCII 97),返回 - 97。
3.2 strncmp () 伪代码实现
// 功能:比较str1和str2的前n个字符,或到'\0'
function strncmp(const char* str1, const char* str2, size_t n) -> int:// 1. 校验指针合法性if str1 == NULL or str2 == NULL:触发未定义行为// 2. 逐字符比较(直到n=0、字符不同或'\0')while n > 0 and *str1 != '\0' and *str2 != '\0':if *str1 != *str2:return (unsigned char)*str1 - (unsigned char)*str2// 指针移动 + 计数递减str1 = str1 + 1str2 = str2 + 1n = n - 1// 3. 处理三种终止情况:n=0、str1到'\0'、str2到'\0'if n == 0:// 已比较完n个字符,视为相同return 0else:// 一方到'\0',返回差值(同strcmp)return (unsigned char)*str1 - (unsigned char)*str2
- n的优先级最高:即使字符未到’\0’,只要n减至 0,立即返回 0;
- 无’\0’安全:若字符串无结束符(如数组存储的固定长度数据),只要n设置为数组长度,就不会越界。
四、典型使用场景:何时用 strcmp (),何时用 strncmp ()
选择函数的核心依据是是否需要完整比较与是否确定字符串有 ‘\0’ ,以下是两类函数的典型应用场景。
4.1 strcmp () 的适用场景
当满足 “字符串以’\0’结尾” 且 “需比较完整内容” 时,优先用strcmp(),代码更简洁。
场景 1:用户名 / 密码验证
用户输入的字符串(如密码)通常以’\0’结尾,需完整匹配才能通过验证:
#include <stdio.h>
#include <string.h>#define CORRECT_USER "admin"
#define CORRECT_PWD "Secure@2025"// 验证用户名密码
int auth_user(const char* input_user, const char* input_pwd) {// 用户名和密码必须都完整匹配if (strcmp(input_user, CORRECT_USER) == 0 && strcmp(input_pwd, CORRECT_PWD) == 0) {return 1; // 验证通过}return 0; // 验证失败
}int main() {char input_user[32], input_pwd[32];printf("请输入用户名:");scanf("%s", input_user);printf("请输入密码:");scanf("%s", input_pwd);if (auth_user(input_user, input_pwd)) {printf("登录成功!\n");} else {printf("用户名或密码错误!\n");}return 0;
}
运行结果:
输入admin和Secure@2025 → 登录成功;
输入admin和Secure@2024 → 密码错误。
场景 2:判断文件后缀名
文件路径字符串以’\0’结尾,需完整匹配后缀名(如.txt):
// 判断文件是否为txt格式
int is_txt_file(const char* file_path) {// 找到最后一个'.'的位置const char* dot = strrchr(file_path, '.');if (dot == NULL) {return 0; // 无后缀名}// 完整比较后缀名是否为".txt"return strcmp(dot, ".txt") == 0;
}
4.2 strncmp () 的适用场景
当需要 “部分比较” 或 “字符串无’\0’” 时,必须用strncmp(),避免越界风险。
场景 1:判断字符串前缀
如判断 URL 是否为 HTTP/HTTPS 协议(只需比较前 7 个字符"http://“或前 8 个"https://”):
#include <stdio.h>
#include <string.h>// 判断URL是否为HTTP/HTTPS协议
int is_http_proto(const char* url) {if (url == NULL) return 0;// 比较前7个字符(http://)或前8个(https://)return strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0;
}int main() {char urls[][64] = {"http://blog.example.com","https://github.com","ftp://file.example.net","https://127.0.0.1:8080"};for (int i = 0; i < 4; i++) {if (is_http_proto(urls[i])) {printf("[%s] 是HTTP/HTTPS协议\n", urls[i]);} else {printf("[%s] 非HTTP/HTTPS协议\n", urls[i]);}}return 0;
}
运行结果:
场景 2:固定长度字段比较
硬件通信、数据库存储中,常以固定长度数组存储字符串(无’\0’),需指定长度比较:
运行结果:
五、关键注意事项
strcmp()与strncmp()的很多 bug 源于忽略细节,以下是必须牢记的 5 个注意事项。
5.1 strcmp () 必须依赖 ‘\0’,否则越界
strcmp()会一直遍历内存直到’\0’,若字符串无结束符,会触发缓冲区越界访问(未定义行为):
解决方法:
- 定义数组时预留’\0’位置:char str1[4] = {‘a’,‘b’,‘c’,‘\0’};
- 用strncmp()指定长度:strncmp(str1, str2, 3)。
5.2 strncmp () 的 n 是无符号整数,不可传负数
n的类型是size_t(无符号),若传入负数,会被强制转换为极大的正数(如 - 1→4294967295),导致越界:
#include <stdio.h>
#include <string.h>int main() {char str1[] = "abc";char str2[] = "abd";int n = -1; // 错误:负数转换为size_t最大值// 风险:比较4294967295个字符,必然越界int result = strncmp(str1, str2, n);printf("结果:%d\n", result);return 0;
}
解决方法:
确保n是非负数:用n = (n < 0) ? 0 : n做防御;
用sizeof()获取固定长度:如strncmp(str1, str2, sizeof(str1)-1)(减 1 是预留’\0’位置)。
5.3 返回值不是只有 - 1、0、1
C 标准仅规定返回值的符号(正 / 负 / 零),未规定具体数值。不同编译器实现可能返回 “第一个不同字符的 ASCII 差值”:
#include <stdio.h>
#include <string.h>int main() {// 'a' ASCII=97,'c'=99 → 差值为-2printf("strcmp(\"a\", \"c\") = %d\n", strcmp("a", "c")); // 'x'=120,'m'=109 → 差值为11printf("strcmp(\"x\", \"m\") = %d\n", strcmp("x", "m")); return 0;
}
GCC 编译器运行结果:
strcmp("a", "c") = -2
strcmp("x", "m") = 11
错误写法:if (strcmp(a, b) == -1) → 某些编译器可能返回 - 2,导致判断失效;
正确写法:if (strcmp(a, b) < 0)。
5.4 不可比较多字节字符串(如中文)
strcmp()与strncmp()均按单字节 ASCII 码比较,而中文(如 GBK、UTF-8)是多字节编码,比较结果会出错:
#include <stdio.h>
#include <string.h>int main() {// UTF-8中,"中"占3字节,"国"占3字节char str1[] = "中";char str2[] = "国";// 错误:比较的是第一个字节的ASCII值,而非汉字本身int result = strcmp(str1, str2);printf("strcmp(\"中\", \"国\") = %d\n", result); return 0;
}
运行结果:strcmp(“中”, “国”) = -32(无实际意义)
解决方法:使用宽字符函数wcscmp()(比较wchar_t类型字符串)或专门的多字节比较库(如iconv)。
5.5 避免传入 NULL 指针
两个函数均未处理NULL参数,传入NULL会触发段错误(程序崩溃):
#include <string.h>int main() {// 错误:str2为NULL,触发段错误strcmp("abc", NULL); return 0;
}
解决方法:加指针合法性校验:
int safe_strcmp(const char* a, const char* b) {if (a == NULL && b == NULL) return 0; // 两者都为NULL,视为相等if (a == NULL) return -1; // a为NULL,视为小于bif (b == NULL) return 1; // b为NULL,视为小于areturn strcmp(a, b);
}
六、函数差异对比:一张表看懂核心区别
为方便快速查阅,以下表格总结了strcmp()与strncmp()的关键差异:
七、完整示例代码:综合场景实践
以下示例结合文件后缀判断(strcmp())与 URL 前缀判断(strncmp()),展示两个函数的协同使用:
#include <stdio.h>
#include <string.h>
#include <stdbool.h>// 1. 用strcmp()判断文件是否为图片(.jpg/.png)
bool is_image_file(const char* file_path) {if (file_path == NULL) return false;const char* dot = strrchr(file_path, '.');if (dot == NULL) return false;return strcmp(dot, ".jpg") == 0 || strcmp(dot, ".png") == 0;
}// 2. 用strncmp()判断URL是否为HTTPS(需比较前8个字符)
bool is_https_url(const char* url) {if (url == NULL) return false;// 确保URL长度≥8,避免n超过字符串长度(可选防御)if (strlen(url) < 8) return false;return strncmp(url, "https://", 8) == 0;
}// 3. 综合判断:是否为HTTPS链接的图片文件
bool is_https_image(const char* https_url) {return is_https_url(https_url) && is_image_file(https_url);
}int main() {char test_urls[][128] = {"https://blog.example.com/photo1.jpg","http://example.com/photo2.png","https://img.example.net/logo.svg","ftp://file.example.com/doc.pdf","https://example.com/avatar.jpg"};printf("=== HTTPS图片链接检测结果 ===\n");for (int i = 0; i < 5; i++) {const char* url = test_urls[i];if (is_https_image(url)) {printf("[√] %s → 是HTTPS图片链接\n", url);} else {printf("[×] %s → 不是HTTPS图片链接\n", url);}}return 0;
}
运行结果:
strcmp()与strncmp()是 C 语言字符串操作的基础工具,两者的核心差异在于 “是否控制比较范围”。实际开发中,需根据 “是否有’\0’” 和 “是否需完整比较” 选择函数 —— 不确定场景下,strncmp()的安全性更高(只要n设置合理)。掌握本文的实现逻辑、注意事项与场景选择原则,能帮你避免 90% 以上的字符串比较 bug,写出更健壮的 C 语言代码。