strcmp()的歧义行为

4
请注意,我已经查看了与该标题相关的相关问题,但从我的角度来看,它们与此问题无关。
最初,我认为程序1和程序2会给我相同的结果。
//Program 1

char *a = "abcd";
char *b = "efgh";
printf("%d", strcmp(a,b));


//Output: -4


//Program 2
printf("%d", strcmp("abcd", "efgh"));

//Output: -1

我所发现的唯一区别是,在程序2中我传递了字符串字面量,而在程序中我传递了 char * 作为 strcmp() 函数的参数。为什么这些看起来相同的程序行为有差异?平台:Linux mint 编译器:g++。编辑:实际上,程序1总是打印第一个不匹配字符的 ASCII 码差异,但是如果字符串2中第一个不匹配字符的 ASCII 码大于字符串1,则程序2会打印-1,反之亦然。

2
“strcmp” 返回一个小于0、等于0或大于0的值。除了0以外实际值没有指定。 - Weather Vane
2
它们都是正确的。其余的都不重要。[但如果你真的想知道:检查汇编器输出] - wildplasser
2
@AjayMishra 这个行为并不含糊。它返回一个负值,这正是规范所要求的。 - Jabberwocky
2
这个问题并不含糊。标准所保证的唯一事情就是返回值将小于、等于或大于0。没有人关心某些具体示例的确切值。它们是无关紧要的,你不能编写代码假设-4或-1更“正确”。 - Blastfurnace
4
这句话的意思是:它不是含糊不清的,而是明确地小于0。编写编译器代码的人并不在意,会返回最容易获得的任何重要值。没有必要保持一致性。 - Weather Vane
显示剩余7条评论
3个回答

6
这是你的C代码:
int x1()
{
  char *a = "abcd";
  char *b = "efgh";
  printf("%d", strcmp(a,b));
}

int x2()
{
  printf("%d", strcmp("abcd", "efgh"));
}

以下是这两个函数生成的汇编输出:

.LC0:
        .string "abcd"
.LC1:
        .string "efgh"
.LC2:
        .string "%d"
x1:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], OFFSET FLAT:.LC0
        mov     QWORD PTR [rbp-16], OFFSET FLAT:.LC1
        mov     rdx, QWORD PTR [rbp-16]
        mov     rax, QWORD PTR [rbp-8]
        mov     rsi, rdx
        mov     rdi, rax
        call    strcmp              // the strcmp function is actually called
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 0
        call    printf
        nop
        leave
        ret

x2:
        push    rbp
        mov     rbp, rsp
        mov     esi, -1             // strcmp is never called, the compiler
                                    // knows what the result will be and it just
                                    // uses -1
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 0
        call    printf
        nop
        pop     rbp
        ret

编译器看到 strcmp("abcd", "efgh") 时,它预先知道结果,因为它知道 "abcd""efgh" 之前。
但是如果看到 strcmp(a,b) ,它不知道结果,因此实际调用了 strcmp 函数的代码被生成。
在另一个编译器或不同的编译器设置下,情况可能会有所不同。至少在初学者水平上,您确实不应该关心这些细节。

知道之前会如何影响行为? - xrfxlp
2
@AjayMishra 无论任何情况下,strcmp("abcd", "efgh")都将返回一个负值。编译器足够聪明,可以找出这一点,只需将对strcmp的调用替换为返回负值的代码,本例中为-1;它也可以返回另一个负值,如-2。编译器不会生成与您编写的C代码一一对应的代码,但它会生成与您编写的C代码行为相同的代码。 - Jabberwocky
@Jabberwocky +1 很好的回答,如果你在你的评论中添加“编译器不应该生成与您编写的C代码一一对应的代码,但它应该生成与您编写的C代码行为相同的代码”,那就更完美了 :-) - some user

2

确实令人惊讶的是,strcmp 对这些调用返回了两个不同的值,但它与 C 标准并不不兼容:

strcmp() 如果第一个字符串在字典顺序上位于第二个字符串之前,则返回负值。-4 和 -1 都是负值。

正如其他人所指出的,生成不同调用的代码也是不同的:

  • 编译器在第一个程序中生成对库函数的调用
  • 编译器能够确定比较结果,并为第二种情况生成显式结果 -1,其中两个参数都是字符串字面量。

为了执行此编译时评估,strcmp 必须以微妙的方式在 <string.h> 中定义,以便编译器可以确定程序引用的是 C 库的实现,而不是可能行为不同的替代品。在最近的 GNU libc 包含文件中跟踪相应的原型有点困难,因为有许多嵌套的宏最终导致了一个隐藏的原型。

请注意,较新版本的gcc和clang都会在两种情况下执行优化,可以在Godbolt Compiler Explorer上进行测试,但它们都不会将此优化与printf的优化相结合,以生成更紧凑的代码puts("-1");。它们似乎只会将printf转换为没有参数的字符串字面格式的puts

-4 是第一个不匹配字母的 ASCII 字符之间的差异。 - xrfxlp
@AjayMishra 在这种情况下是正确的,但它可以是任何东西。它甚至可以是INT_MIN,尽管这不太可能。 - S.S. Anne

0

我相信(需要查看并解释机器代码)有一个版本可以在不调用库中的代码的情况下工作(就像你写了printf("%d", -1);一样)。


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