strcmp有什么问题?

17
在对问题Reading In A String and comparing it C的回答中,有不止一个人反对使用strcmp(),并表示类似以下的话:

我也强烈建议你现在就开始习惯使用strncmp(),以避免今后出现许多问题。

或者(在Why does my string comparison fail?中):

确保使用strncmp而不是strcmp。strcmp非常不安全。

他们指的是什么问题?

scanf()使用字符串格式化符号gets()都不被推荐使用,因为它们几乎总是导致缓冲区溢出漏洞。但是,使用strcmp()不可能导致缓冲区溢出,对吗?

"缓冲区溢出是一种异常情况,程序在向缓冲区写入数据时,越过了缓冲区的边界并覆盖了相邻的内存。"

( -- 维基百科:缓冲区溢出).

由于strcmp()函数从不写入任何缓冲区,所以strcmp()函数不会导致缓冲区溢出,对吗?

为什么人们不推荐使用strcmp(),而是推荐使用strncmp()呢?


9
你为什么不问那些人呢? - Kerrek SB
13
相信我,strncmp()strcmp() 一样好(或不好)。 - P.P
1
链接的SO文章上的评论大多是垃圾。 - M.M
1
使用 strncmp 仅仅是为了解决未以 null 结尾的字符串,而并没有解决本质问题,即你有一个未终止的字符串。在下一个假定它以 null 结尾的函数中它只会出现混乱。 - Schwern
3个回答

26

strncmp虽然可以防止你越界访问缓冲区,但它的主要目的并不是为了保证安全。相反,它存在的原因是当一个人想要比较一个(可能以NUL结尾的)字符串的前N个字符时。

man page中可以看到:

strcmp()函数用于比较两个字符串s1s2。如果s1被发现小于、等于或大于s2,则返回整数值小于、等于或大于零。

strncmp()函数类似,除了它只比较s1s2的前N个字节。

需要注意的是,此时不能用简单的memcmp替换strncmp,因为仍然需要利用其停止-NUL行为,以防一些字符串比n还要短。

如果strcmp引起缓冲区越界,则有以下两种可能:

  1. 您的数据不应该以NUL结尾,应该使用memcmp代替。
  2. 您的数据应该以NUL结尾,但在填充缓冲区时已经犯了错误,即没有正确地添加NUL。

需要注意的是,读取缓冲区末端之外的内容仍被认为是缓冲区越界。虽然可能看起来无害,但其危害程度与写入缓冲区末端之外的内容一样严重。

阅读、写入、执行......这都不重要。任何意外地址的内存引用都会导致未定义的行为。在最明显的情况下,您试图访问未映射到进程地址空间中的页面,从而导致页面故障和随后的SIGSEGV。在最糟糕的情况下,有时会遇到\0字节,但有时会遇到其他缓冲区,导致程序行为不一致。


我不明白strcmp()如何会导致缓冲区溢出,即使这两个条件都成立。您能否多说几句关于到底出了什么问题? - David Cary
假设您有一个char buf [100],其中每个字符都是'a'(它没有以NUL结尾)。如果您将此缓冲区传递给strcmp(假设另一个参数是更长的字符串),则strcmp将继续在buf [100]等位置进行比较,从而越过缓冲区。 - Jonathon Reinhart
1
我能理解在缓冲区末尾写入数据会导致问题。但是strcmp()不会这样做,对吗?您是否介意在回答中添加一些关于如果strcmp()继续读取超出缓冲区末尾会发生什么的说明? - David Cary
1
读取,写入,执行...这都不重要。任何指向意外地址的内存引用都是未定义行为。在最明显的情况下,您尝试访问未映射到进程地址空间的页面,导致页故障和后续的SIGSEGV。在最坏的情况下,有时会遇到\0字节,但其他时候可能会遇到其他缓冲区,从而导致程序行为不稳定。 - Jonathon Reinhart
3
“it exists for the case where one wants to compare only the first N characters of a (properly NUL-terminated) string.”这句话是不正确的。根据C规范,“strncmp函数返回一个整数...根据s1指向的可能以空字符结尾的数组”。int strncmp(const char *s1, const char *s2, size_t n);中的s1s2都不需要是C字符串。它们可以是简单的字符串,也可以是未经过“适当的NUL结尾”的char数组。 - chux - Reinstate Monica
显示剩余3条评论

7

按照定义,字符串是“由连续字符序列组成,并以包括第一个空字符为结尾”的。

只有在比较两个字符数组作为字符串时,您确定两个数组至少都有n个字节长(传递给strncmp()的第三个参数),并且您不确定两个数组是否包含字符串(即包含'\0'空字符终止符)时,strncmp()才比strcmp()更安全。

在大多数情况下,如果您的代码(如果正确的话)将保证任何应该包含空字符结尾的数组实际上都包含空字符结尾的字符串。

strncmp()中添加的n并不能使不安全的代码变得安全。它不能防止空指针、未初始化的指针、未初始化的数组、错误的n值或仅传递错误数据。您可能会用这两个函数中的任何一个自我伤害。

如果您试图使用您认为包含空字符结尾的数组调用strcmpstrncmp,但实际上并不包含,则您的代码已经存在漏洞。使用strncmp()可能会帮助您避免该漏洞的直接症状,但它不会修复它。


2

strcmp函数会逐个字符比较两个字符串,直到发现差异或在其中一个字符串中找到了\0

相反的,strncmp提供了一种限制要比较的字符数的方式,因此如果字符串不以\0结尾,该函数将在达到大小限制后停止继续检查。

想象一下,如果您正在比较这两个内存区域中的两个字符串:

0x40, 0x41, 0x42,... 0x40, 0x41, 0x42,...

而您只对前两个字符感兴趣。某种程度上,\0已经从字符串末尾删除,并且第三个字节恰好在两个区域重合。如果num参数为2,则strncmp将避免比较此第三个字节。

编辑 正如下面的评论所指出的,这种情况源于语言的错误或非常具体的用法。


9
如果想要比较内存区域,请使用memcmp。在C语言中,“字符串”是以空字符结尾的字符序列。如果您有字符串,请使用strcmp进行比较。如果没有字符串,则不需要使用。 - Kerrek SB
1
当然,strcmp可以使比较“安全”,但这实际上只是推迟了问题。你的非空终止字符串随后会导致程序出现未定义行为。 - Oliver Charlesworth
@OliCharlesworth 我同意,只是想指出一种情况,strncmpstrcmp更安全。 - Pablo Francisco Pérez Hidalgo
5
据我理解,strncmp 的存在并非出于"安全"的考虑,而是为了"比较这些字符串的前N个字符"。 - Jonathon Reinhart
2
@JonathonReinhart:你应该把这个作为一个答案。 - Oliver Charlesworth
5
不以\0结尾的内容不是字符串。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接