一段登录校验代码,线上一直走「用户名不存在」分支,本地调试却正常。最后发现是 if (strcmp(a, b)) 写反了,相等时返回 0,条件为假。

原型和返回值

int strcmp(const char *s1, const char *s2),两个字符串按字节比,返回小于 0、等于 0、大于 0 三种结果。注意不是固定返回 -1 和 1,标准只规定正负,gcc 的实现常直接返回两个字符的差值。所以别写成 if (strcmp(a, b) == 1)。

比较的是无符号字节

char 在 x86 上默认有符号,字符编码大于 127 时,实现要先转成 unsigned char 再比,否则结果在不同平台上不一致。中文按 UTF-8 存,一个字三字节,strcmp 只按编码顺序排,得到的顺序和拼音毫无关系,想按拼音排得用 strcoll。

常见写错的几种

  • if (strcmp(a, b)) 用来判断相等,语义正好反了
  • 拿返回值当长度用,差值是编码之差,没有意义
  • 比较前没判空指针,某个串是 NULL 直接崩
  • 用双等号比较两个 char 数组,比的是地址不是内容

正确写法是 if (strcmp(a, b) == 0)。要忽略大小写用 strcasecmp,Windows 上叫 _stricmp。

只比前缀就用 strncmp

strncmp(a, b, n) 最多比 n 个字节,判断前缀比 strcmp 安全。但它遇到空字符就停,两个串前 n 字节相同、其中一个更短时,返回值仍由差值决定。解析协议头时先用 strlen 确认长度,再调 strncmp。

密码比较别直接用

strcmp 遇到第一个不同字节就返回,耗时会随相同前缀变长,理论上能被计时攻击利用。比较哈希值要用恒定时间的函数,OpenSSL 里有 CRYPTO_memcmp,自己写就是从头到尾异或累加,别中途 return。另外,比较前先用 strlen 拿到真实长度,别让缓冲区末尾的垃圾字节参与比较。

strcmp 本身没坑,坑在于把「非零为真」当成了「相等为真」。写完顺手跑一遍空串、单字符、长串三个用例,比事后排查省事。