strcmp() 的不明确行为

Ambiguous behaviour of strcmp()

请注意,我已经检查了与此标题相关的问题,但在我看来它们与此问题无关。

最初我以为程序 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

我能发现的唯一区别是,在 program2 中我传递了字符串文字,而在程序中我传递了 char * 作为 strcmp() 函数的参数。

为什么这些看似相同的程序在行为上存在差异?

平台:Linux薄荷 编译器:g++

编辑:实际上程序1总是打印第一个不匹配字符的ascii码的差异,但是如果string2中第一个不匹配字符的ascii码大于string1 反之亦然。

确实令人惊讶 strcmp returns 这些调用有 2 个不同的值,但它并不与 C 标准不兼容:

strcmp() returns 如果第一个字符串按字典顺序在第二个字符串之前,则为负值。 -4和-1都是负值。

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

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

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

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

这是你的 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.

的代码

使用其他编译器或使用不同的编译器设置,情况可能会有所不同。至少在初学者的水平上,你真的不应该关心这些细节。

我相信(需要查看(和解释)机器代码)一个版本无需调用库中的代码即可运行(就好像您编写了 printf("%d", -1);)。