BSD memcmp(3) 手册和实现的区别
BSD memcmp(3) difference between manual and implementation
根据 man memcmp
关于 OSX 达尔文的说法:
The memcmp() function returns zero if the two strings are identical, otherwise
returns the difference between the first two differing bytes (treated as unsigned
char values, so that 0
is greater than [=13=]
, for example). Zero-length strings
are always identical. This behavior is not required by C and portable code should
only depend on the sign of the returned value.
然而,当我测试这个时:
#include <stdio.h>
#include <string.h>
int main()
{
printf("%i\n", memcmp("0", "[=10=]", 1));
return (0);
}
显示-1
,表示0
小于[=13=]
。
有什么解释吗?
根据gcc --version
的编译器版本是"Apple LLVM version 9.0.0 (clang-900.0.39.2)",系统是运行 High Sierra 10.13.4
这是手册中的错误。它描述了 strcmp()
,当它到达其中一个字符串中的零字节时停止比较,因为那是字符串终止符;较长的字符串将被视为更大("foobar"
大于 "foo"
)。但是 memcmp()
用于比较任意内存区域,而不是字符串,因此不会特殊处理零字节。
然而,这并不能解释为什么 memcmp()
返回 -1
。它应该比较 '0'
和 '[=22=]'
,并返回一个正值。似乎达尔文 memcmp()
将它们比较为 signed char
而不是 unsigned char
,因此 '0'
是 -128
而不是 128
。如果第一个字符串是从 "0"
到 "7"
的任何内容,它 returns 这个不正确的结果。
当我在 Linux 上尝试您的代码时,我得到 1
而不是 -1
。所以这似乎是达尔文库中的一个错误。还有手册页中的错误,因为它说它们被比较为 unsigned char
.
我试过这个程序:
#include <stdio.h>
#include <string.h>
int main()
{
printf("memcmp: %i\n", memcmp("0", "[=10=]", 1));
printf("bcmp: %i\n", bcmp("0", "[=10=]", 1));
printf("strcmp: %i\n", strcmp("0", "[=10=]"));
return (0);
}
在 Mac OS High Sierra 上打印:
memcmp: -1
bcmp: 128
strcmp: 128
在 Debian Linux 我得到:
memcmp: 1
bcmp: 1
strcmp: 1
手册页中提到的零长度字符串也不正确。 "[=34=]abc"
和 "[=35=]def"
都是零长度字符串,因为字符串在逻辑上以空字节结束。但他们比较不同 memcmp()
printf("memcmp: %i\n", memcmp("[=13=]abc", "[=13=]def", 4));
printf("bcmp: %i\n", bcmp("[=13=]abc", "[=13=]def", 4));
printf("strcmp: %i\n", strcmp("[=13=]abc", "[=13=]def"));
打印:
memcmp: -1
bcmp: -3
strcmp: 0
您的 memcmp
.
的特定实现中似乎存在错误
我在我的 OSX/Darwin 系统上试过你的程序,得到了一个正数。所以我的系统没有这个bug。
不过,奇怪的是,我系统上的行为会因我使用 clang
还是 gcc
而有所不同。我以为他们使用了相同的库,但是 clang
给出了 128 而 gcc
给出了 1。(也许 memcmp
是作为内置编译器实现的。)
此外,顺便说一下,我的系统上的 man memcmp
没有 "This behavior is not required by C" 句子。
这是一个编译器错误。当两个参数都是文字时,编译器会错误地评估对 memcmp
的调用。当实际调用 memcmp
时,它 return 是预期的结果。
以下是在 macOS 10.13.4 (17E199) 上使用 Apple LLVM 版本 9.1.0 (clang-902.0.39.1) 测试的。我使用“clang -std=c11”、“-O0”或“-O3”编译到 select 优化级别,并使用“-S”生成程序集。
考虑对 memcmp
的四种替代调用:
printf("%i\n", memcmp("0", "[=10=]", 1));
printf("%i\n", memcmp((char[] ) { '0' }, "[=10=]", 1));
printf("%i\n", memcmp((unsigned char[] ) { '0' }, "[=10=]", 1));
char a[1] = { 128 };
char b[1] = { 0 };
printf("%i\n", memcmp(a, b, 1));
对于前两个调用,编译器生成 不正确的 程序集,该程序集将 −1 的硬编码值传递给 printf
。没有调用memcmp
;它已被优化掉,即使在“-O0”版本中也是如此。 (在“-O0”版本中,-1 被编码为 4294967295,这在其上下文中是等效的。)当使用字符串文字或复合文字调用 memcmp
时,其 return 值在编译时间,所以编译器已经评估了它。但是,这样做是错误的。
对于第三次调用,编译器生成 不正确的 程序集,传递硬编码值 1。这表明编译器在其评估中(错误地)使用了文字类型.
对于第四次调用,我们使用非文字的定义对象,“-O0”版本调用 memcmp
。当 运行 时,程序打印 correct 结果 128。对于“-O3”版本,编译器生成 correct 程序集硬编码值 128。因此编译器 确实 有一个算法可以在编译时正确评估 memcmp
,但它对文字的情况使用了不同的错误算法.
当使用一种文字和一种非文字时,编译器会生成正确的代码。这解释了为什么以前没有发现和修复此错误:使用两个文字调用 memcmp
的情况很少见,同时执行此操作并取决于结果的大小或使用设置了高位的字符的代码更为罕见。
(我向 Apple 报告了这个错误。)
根据 man memcmp
关于 OSX 达尔文的说法:
The memcmp() function returns zero if the two strings are identical, otherwise returns the difference between the first two differing bytes (treated as unsigned char values, so that
0
is greater than[=13=]
, for example). Zero-length strings are always identical. This behavior is not required by C and portable code should only depend on the sign of the returned value.
然而,当我测试这个时:
#include <stdio.h>
#include <string.h>
int main()
{
printf("%i\n", memcmp("0", "[=10=]", 1));
return (0);
}
显示-1
,表示0
小于[=13=]
。
有什么解释吗?
根据gcc --version
的编译器版本是"Apple LLVM version 9.0.0 (clang-900.0.39.2)",系统是运行 High Sierra 10.13.4
这是手册中的错误。它描述了 strcmp()
,当它到达其中一个字符串中的零字节时停止比较,因为那是字符串终止符;较长的字符串将被视为更大("foobar"
大于 "foo"
)。但是 memcmp()
用于比较任意内存区域,而不是字符串,因此不会特殊处理零字节。
然而,这并不能解释为什么 memcmp()
返回 -1
。它应该比较 '0'
和 '[=22=]'
,并返回一个正值。似乎达尔文 memcmp()
将它们比较为 signed char
而不是 unsigned char
,因此 '0'
是 -128
而不是 128
。如果第一个字符串是从 "0"
到 "7"
的任何内容,它 returns 这个不正确的结果。
当我在 Linux 上尝试您的代码时,我得到 1
而不是 -1
。所以这似乎是达尔文库中的一个错误。还有手册页中的错误,因为它说它们被比较为 unsigned char
.
我试过这个程序:
#include <stdio.h>
#include <string.h>
int main()
{
printf("memcmp: %i\n", memcmp("0", "[=10=]", 1));
printf("bcmp: %i\n", bcmp("0", "[=10=]", 1));
printf("strcmp: %i\n", strcmp("0", "[=10=]"));
return (0);
}
在 Mac OS High Sierra 上打印:
memcmp: -1
bcmp: 128
strcmp: 128
在 Debian Linux 我得到:
memcmp: 1
bcmp: 1
strcmp: 1
手册页中提到的零长度字符串也不正确。 "[=34=]abc"
和 "[=35=]def"
都是零长度字符串,因为字符串在逻辑上以空字节结束。但他们比较不同 memcmp()
printf("memcmp: %i\n", memcmp("[=13=]abc", "[=13=]def", 4));
printf("bcmp: %i\n", bcmp("[=13=]abc", "[=13=]def", 4));
printf("strcmp: %i\n", strcmp("[=13=]abc", "[=13=]def"));
打印:
memcmp: -1
bcmp: -3
strcmp: 0
您的 memcmp
.
我在我的 OSX/Darwin 系统上试过你的程序,得到了一个正数。所以我的系统没有这个bug。
不过,奇怪的是,我系统上的行为会因我使用 clang
还是 gcc
而有所不同。我以为他们使用了相同的库,但是 clang
给出了 128 而 gcc
给出了 1。(也许 memcmp
是作为内置编译器实现的。)
此外,顺便说一下,我的系统上的 man memcmp
没有 "This behavior is not required by C" 句子。
这是一个编译器错误。当两个参数都是文字时,编译器会错误地评估对 memcmp
的调用。当实际调用 memcmp
时,它 return 是预期的结果。
以下是在 macOS 10.13.4 (17E199) 上使用 Apple LLVM 版本 9.1.0 (clang-902.0.39.1) 测试的。我使用“clang -std=c11”、“-O0”或“-O3”编译到 select 优化级别,并使用“-S”生成程序集。
考虑对 memcmp
的四种替代调用:
printf("%i\n", memcmp("0", "[=10=]", 1));
printf("%i\n", memcmp((char[] ) { '0' }, "[=10=]", 1));
printf("%i\n", memcmp((unsigned char[] ) { '0' }, "[=10=]", 1));
char a[1] = { 128 };
char b[1] = { 0 };
printf("%i\n", memcmp(a, b, 1));
对于前两个调用,编译器生成 不正确的 程序集,该程序集将 −1 的硬编码值传递给 printf
。没有调用memcmp
;它已被优化掉,即使在“-O0”版本中也是如此。 (在“-O0”版本中,-1 被编码为 4294967295,这在其上下文中是等效的。)当使用字符串文字或复合文字调用 memcmp
时,其 return 值在编译时间,所以编译器已经评估了它。但是,这样做是错误的。
对于第三次调用,编译器生成 不正确的 程序集,传递硬编码值 1。这表明编译器在其评估中(错误地)使用了文字类型.
对于第四次调用,我们使用非文字的定义对象,“-O0”版本调用 memcmp
。当 运行 时,程序打印 correct 结果 128。对于“-O3”版本,编译器生成 correct 程序集硬编码值 128。因此编译器 确实 有一个算法可以在编译时正确评估 memcmp
,但它对文字的情况使用了不同的错误算法.
当使用一种文字和一种非文字时,编译器会生成正确的代码。这解释了为什么以前没有发现和修复此错误:使用两个文字调用 memcmp
的情况很少见,同时执行此操作并取决于结果的大小或使用设置了高位的字符的代码更为罕见。
(我向 Apple 报告了这个错误。)