【问题标题】:Ambiguous behaviour of strcmp()strcmp() 的模棱两可的行为
【发布时间】:2020-06-03 23:18:44
【问题描述】:

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

最初我认为 program1 和 program2 会给我相同的结果。

//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 mint 编译器:g++

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

【问题讨论】:

  • strcmp 返回一个 0 的值。除 0 外未指定实际值。
  • 他们都是正确的。其余的无关紧要。 [但如果你真的想知道:检查汇编输出]
  • @AjayMishra 的行为并不模棱两可。它返回一个 negative 值,这就是规范所说的应该做的。
  • 没有任何含糊之处。标准唯一保证的是返回值将小于、等于或大于 0。没有人关心某些特定示例的确切值。它们是不相关的,你不能假设 -4 或 -1 更“正确”来编写代码。
  • 这不是模棱两可的,它是无可争议的

标签: c string strcmp


【解决方案1】:

这是你的 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的代码。

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

【讨论】:

  • 事先知道如何影响行为?
  • @AjayMishra strcmp("abcd", "efgh") 在任何情况下都会总是返回一个负值。编译器足够聪明地解决这个问题,只需将调用strcmp 替换为返回负值的代码,在这种情况下为-1;它也可以返回-2,这也是一个负值。编译器不会生成与您编写的 C 代码一对一翻译的代码,但它会生成与您编写的 C 代码行为相同的代码。
  • @Jabberwocky +1 不错的答案,添加 编译器不应 生成与您编写的 C 代码一对一翻译的代码,但它预计会生成与您编写的 C 代码相同的代码。 从您的评论到您的答案将使其完美:-)
【解决方案2】:

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

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

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

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

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

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

【讨论】:

  • -4 是第一个不匹配字母的ascii字符之间的差异
  • @AjayMishra 在这种情况下这是真的,但它可以是任何东西。它甚至可能是INT_MIN,尽管这不太可能。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-09
    • 2011-01-09
    • 1970-01-01
    • 2014-05-17
    • 1970-01-01
    • 2017-12-14
    • 2012-08-14
    • 2020-08-25
    相关资源
    最近更新 更多