BSD memcmp(3)手动和实现之间的区别

时间:2018-05-20 08:49:09

标签: c macos darwin

根据OSX Darwin的man memcmp

  

如果两个字符串相同,则memcmp()函数返回零,否则返回0        返回前两个不同字节之间的差异(视为无符号        char值,例如\200大于\0。零长度字符串        永远都是一样的。 C和可移植代码应该不需要此行为        只取决于返回值的符号。

但是,当我测试时:

#include <stdio.h>
#include <string.h>

int main()
{
    printf("%i\n", memcmp("\200", "\0", 1));
    return (0);
}

显示-1,表示\200小于\0

对此有任何解释吗?

根据gcc --version的编译器版本是“Apple LLVM版本9.0.0(clang-900.0.39.2)”,系统正在运行High Sierra 10.13.4

3 个答案:

答案 0 :(得分:2)

您的memcmp

的特定实现中存在一个错误

我在我的OSX / Darwin系统上尝试了你的程序,得到了一个正数。所以我的系统没有错误。

但奇怪的是,我系统上的行为会有所不同,具体取决于我使用clang还是gcc。我认为他们使用了相同的库,但是clang给出了128而gcc给出了1.(或许memcmp被实现为内置在其中一个上的编译器。)

另外,顺便提一下,我系统上的man memcmp没有&#34; C&#34;不需要这种行为。句。

答案 1 :(得分:2)

这是编译器错误。当两个参数都是文字时,编译器会错误地评估对memcmp的调用。实际调用memcmp时,它会返回预期结果。

以下是在macOS 10.13.4(17E199)上使用Apple LLVM版本9.1.0(clang-902.0.39.1)进行测试的。我用“clang -std = c11”编译,用“-O0”或“-O3”选择优化级别,用“-S”编译生成汇编。

考虑对memcmp的四个替代调用:

    printf("%i\n", memcmp("\200", "\0", 1));

    printf("%i\n", memcmp((char[] ) { '\200' }, "\0", 1));

    printf("%i\n", memcmp((unsigned char[] ) { '\200' }, "\0", 1));

    char a[1] = { 128 };
    char b[1] = { 0 };
    printf("%i\n", memcmp(a, b, 1));

对于前两次调用,编译器会生成不正确的程序集,该程序集将硬编码值-1传递给printf。没有致电memcmp;即使在“-O0”版本中,它也已经过优化。 (在“-O0”版本中,-1编码为4294967295,在其上下文中是等效的。)当使用字符串文字或复合文字调用memcmp时,其返回值在编译时是已知的,因此编译器对它进行了评估。但是,它做错了。

对于第三次调用,编译器生成不正确的程序集,该程序集传递硬编码值1.这表明编译器(错误地)在其评估中使用了文字的类型。

对于第四次调用,我们使用不是文字的定义对象,“-O0”版本调用memcmp。运行时,程序打印正确结果,128。对于“-O3”版本,编译器生成正确程序集,其硬编码值为128.因此编译器< strong>确实有一个算法在编译时正确评估memcmp,但它对文字的情况使用了一个不同的错误算法。

当使用一个文字和一个非文字时,编译器会生成正确的代码。这就解释了为什么以前没有看到和修复过这个错误:用{2}字符调用memcmp的情况很少见,并且代码都是这样做的,取决于结果的大小或使用设置了高位的字符的情况比较少见。

(我向Apple报告了这个错误。)

答案 2 :(得分:-1)

这是手册中的错误。它描述了strcmp(),当它到达其中一个字符串中的零字节时停止比较,因为那是字符串终止符;然后,较长的字符串将被视为更大("foobar"大于"foo")。但memcmp()用于比较任意内存区域,而不是字符串,因此不会特别处理零字节。

但是,这并不能解释memcmp()返回-1的原因。应该比较'\200''\0',并返回正值。看起来Darwin memcmp()将它们比作signed char而不是unsigned char,因此'\200'-128而不是128。如果第一个字符串是从"\200""\377"的任何内容,则会返回此错误结果。

当我在Linux上尝试您的代码时,我得到1而不是-1。所以这似乎是达尔文图书馆的一个错误。还有手册页中的一个错误,因为它说它们被比作unsigned char

我试过这个程序:

#include <stdio.h>
#include <string.h>

int main()
{
    printf("memcmp: %i\n", memcmp("\200", "\0", 1));
    printf("bcmp: %i\n", bcmp("\200", "\0", 1));
    printf("strcmp: %i\n", strcmp("\200", "\0"));
    return (0);
}

在Mac OS High Sierra上打印:

memcmp: -1
bcmp: 128
strcmp: 128
在Debian Linux上我得到了:

memcmp: 1
bcmp: 1
strcmp: 1

手册页中提到零长度字符串也是不正确的。 "\0abc""\0def"都是零长度字符串,因为字符串逻辑上以空字节结束。但他们与memcmp()

进行了比较
printf("memcmp: %i\n", memcmp("\0abc", "\0def", 4));
printf("bcmp: %i\n", bcmp("\0abc", "\0def", 4));
printf("strcmp: %i\n", strcmp("\0abc", "\0def"));

打印:

memcmp: -1
bcmp: -3
strcmp: 0