【问题标题】:Why the returns of strcmp is different? [duplicate]为什么 strcmp 的返回值不同? [复制]
【发布时间】:2013-05-09 04:03:10
【问题描述】:

这是我用 gcc 编译的 C 代码

char *a="a";
char *d="d";
printf("%d\n", strcmp("a", "d"));
printf("%d\n", strcmp(a, "d"));
printf("%d\n", strcmp(a, d));

当我使用-O 编译时,输出是

-1
-3
-1

当我在没有-O 的情况下编译时,输出是

-1
-3
-3

为什么输出不一样,strcmp的代码是什么?

【问题讨论】:

    标签: c strcmp


    【解决方案1】:

    为什么输出不同

    因为重要的是返回值的符号(正数、负数或零)。 strcmp() 不需要返回 +1 或 -1,也不需要返回一致的值。我怀疑在第一种和第三种情况下,编译器优化了对strcmp() 的调用并将-1 放在返回值的位置。在第二种情况下,我认为该函数实际上是被调用的。

    strcmp的代码是什么?

    从它似乎返回第一个不同字符的字符代码之间的差异这一事实推断,我会说这是glibc的strcmp():

    int
     strcmp (p1, p2)
          const char *p1;
          const char *p2;
     {
       register const unsigned char *s1 = (const unsigned char *) p1;
       register const unsigned char *s2 = (const unsigned char *) p2;
       unsigned char c1, c2;
    
       do
         {
           c1 = (unsigned char) *s1++;
           c2 = (unsigned char) *s2++;
           if (c1 == '\0')
         return c1 - c2;
         }
       while (c1 == c2);
    
       return c1 - c2;
     }
    

    编辑: @AndreyT 不相信我,所以这是为我生成的 GCC 4.2 程序集(OS X 10.7.5 64 位 Intel,默认优化级别 - 无标志):

        .section    __TEXT,__text,regular,pure_instructions
        .globl  _main
        .align  4, 0x90
    _main:
    Leh_func_begin1:
        pushq   %rbp
    Ltmp0:
        movq    %rsp, %rbp
    Ltmp1:
        subq    $32, %rsp
    Ltmp2:
        leaq    L_.str(%rip), %rax
        movq    %rax, -16(%rbp)
        leaq    L_.str1(%rip), %rax
        movq    %rax, -24(%rbp)
        movl    $-1, %ecx             ; <- THIS!
        xorb    %dl, %dl
        leaq    L_.str2(%rip), %rsi
        movq    %rsi, %rdi
        movl    %ecx, %esi
        movq    %rax, -32(%rbp)
        movb    %dl, %al
        callq   _printf               ; <- no call to `strcmp()` so far!
        movq    -16(%rbp), %rax
        movq    %rax, %rdi
        movq    -32(%rbp), %rsi
        callq   _strcmp               ; <- strcmp()
        movl    %eax, %ecx
        xorb    %dl, %dl
        leaq    L_.str2(%rip), %rdi
        movl    %ecx, %esi
        movb    %dl, %al
        callq   _printf               ; <- printf()
        movq    -16(%rbp), %rax
        movq    -24(%rbp), %rcx
        movq    %rax, %rdi
        movq    %rcx, %rsi
        callq   _strcmp               ; <- strcmp()
        movl    %eax, %ecx
        xorb    %dl, %dl
        leaq    L_.str2(%rip), %rdi
        movl    %ecx, %esi
        movb    %dl, %al
        callq   _printf               ; <- printf()
        movl    $0, -8(%rbp)
        movl    -8(%rbp), %eax
        movl    %eax, -4(%rbp)
        movl    -4(%rbp), %eax
        addq    $32, %rsp
        popq    %rbp
        ret
    Leh_func_end1:
    
        .section    __TEXT,__cstring,cstring_literals
    L_.str:
        .asciz   "a"
    
    L_.str1:
        .asciz   "d"
    
    L_.str2:
        .asciz   "%d\n"
    
        .section    __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
    EH_frame0:
    Lsection_eh_frame:
    Leh_frame_common:
    Lset0 = Leh_frame_common_end-Leh_frame_common_begin
        .long   Lset0
    Leh_frame_common_begin:
        .long   0
        .byte   1
        .asciz   "zR"
        .byte   1
        .byte   120
        .byte   16
        .byte   1
        .byte   16
        .byte   12
        .byte   7
        .byte   8
        .byte   144
        .byte   1
        .align  3
    Leh_frame_common_end:
        .globl  _main.eh
    _main.eh:
    Lset1 = Leh_frame_end1-Leh_frame_begin1
        .long   Lset1
    Leh_frame_begin1:
    Lset2 = Leh_frame_begin1-Leh_frame_common
        .long   Lset2
    Ltmp3:
        .quad   Leh_func_begin1-Ltmp3
    Lset3 = Leh_func_end1-Leh_func_begin1
        .quad   Lset3
        .byte   0
        .byte   4
    Lset4 = Ltmp0-Leh_func_begin1
        .long   Lset4
        .byte   14
        .byte   16
        .byte   134
        .byte   2
        .byte   4
    Lset5 = Ltmp1-Ltmp0
        .long   Lset5
        .byte   13
        .byte   6
        .align  3
    Leh_frame_end1:
    
    
    .subsections_via_symbols
    

    以及原始源代码:

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        const char *a = "a";
        const char *d = "d";
        printf("%d\n", strcmp("a", "d"));
        printf("%d\n", strcmp(a, "d"));
        printf("%d\n", strcmp(a, d));
    
        return 0;
    }
    

    以及它生成的输出(截图以获得更好的证明):

    【讨论】:

    • 嗯,strcmp 是一个非常简单的函数。无论做什么,都应该很容易合理。 (是的,我说的是实现细节)。我实际上怀疑 OP 的实验以某种方式被破坏了。
    • @AndreyT 对于在 Mac OS X 上使用 GCC 4.2 的我来说,这是打印的 -1 -3 -3,当我查看生成的程序集时,我已经被证明是正确的(真是令人惊讶)。
    • @AndreyT "为什么 strcmp(a, d) 又是 -1? -- 因为允许编译器优化。看我的回答。
    • @JimBalter 没错,看看我在生成的汇编器中的注释——第一个使用两个字符串文字的调用已经被优化掉了。
    • @d0rmLife 优化不添加 -3。优化删除了 -3(在这种特殊情况下是 strcmp() 的实际返回值)并将其替换为 -1(一个非常任意的负数)。
    【解决方案2】:

    C 标准允许实现返回任何负值。只要结果符合标准,它还允许实现对库函数调用进行优化......因此,实现可以通过生成内联机器指令而不是调用函数来优化strcmp 等函数。当参数是常量时,可以进行额外的优化。所以结果不同的原因是因为优化器恰好为某些情况生成了不同的代码。符合标准的程序不允许关心返回哪个负值。

    编辑:

    目前在我的系统上,输出是

    -1
    -3
    -3
    

    这是编译器生成的产生这些结果的代码(使用 gcc -S 获得):

        movl    $-1, 4(%esp)
        movl    $LC2, (%esp)
        call    _printf
        movl    $LC1, 4(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    _strcmp
        movl    %eax, 4(%esp)
        movl    $LC2, (%esp)
        call    _printf
        movl    24(%esp), %eax
        movl    %eax, 4(%esp)
        movl    28(%esp), %eax
        movl    %eax, (%esp)
        call    _strcmp
        movl    %eax, 4(%esp)
    

    如您所见,只有两个strcmp 调用。第一次比较的 -1 结果是在编译时产生的,因为编译器知道“a”小于“d”。如果我使用 -O,它会生成以下代码:

        movl    $-1, 4(%esp)
        movl    $LC0, (%esp)
        call    _printf
        movl    $-1, 4(%esp)
        movl    $LC0, (%esp)
        call    _printf
        movl    $-1, 4(%esp)
        movl    $LC0, (%esp)
        call    _printf
    

    【讨论】:

      【解决方案3】:

      我来了

       -1
       -3
       -1
      

      在 Linux 上使用 GCC 4.1.2 构建优化 (-O4) 的输出。这是编译器为main生成的代码

      main:
      .LFB25:
              subq    $8, %rsp
      .LCFI0:
              movl    $-1, %esi
              xorl    %eax, %eax
              movl    $.LC0, %edi
              call    printf
              movzbl  .LC1(%rip), %edx
              movzbl  .LC2(%rip), %eax
              movl    %edx, %esi
              subl    %eax, %esi
              jne     .L2
              movzbl  .LC1+1(%rip), %esi
              movzbl  .LC2+1(%rip), %eax
              subl    %eax, %esi
      .L2:
              movl    $.LC0, %edi
              xorl    %eax, %eax
              call    printf
              movl    $-1, %esi
              movl    $.LC0, %edi
              xorl    %eax, %eax
              call    printf
              xorl    %eax, %eax
              addq    $8, %rsp
              ret
      

      这意味着第一个和最后一个比较实际上被优化了,而中间比较实际上是通过减法实现的(这就是它产生-3的原因)。我在这种选择性行为中看不到任何逻辑,所以它可能只是优化器的一个怪癖。

      顺便说一句,没有优化同样的 GCC 4.1.2 产生

       -1
       -1
       -1
      

      输出,因为它调用strcmp。 strcmp 在这个标准库中实现为

      <strcmp>           mov    (%rdi),%al
      <strcmp+2>         cmp    (%rsi),%al
      <strcmp+4>         jne    <strcmp+19>
      <strcmp+6>         inc    %rdi
      <strcmp+9>         inc    %rsi
      <strcmp+12>        test   %al,%al
      <strcmp+14>        jne    <strcmp>
      <strcmp+16>        xor    %eax,%eax
      <strcmp+18>        retq
      <strcmp+19>        mov    $0x1,%eax
      <strcmp+24>        mov    $0xffffffff,%ecx
      <strcmp+29>        cmovb  %ecx,%eax
      <strcmp+32>        retq
      

      这意味着它有意实现返回-1、0 或+1,即使它可能被视为次优。

      【讨论】:

      • 这里强调的是,可能有三个不同的strcmp()实现在起作用:在编译器中实现的比较并在编译时完全评估;编译器提供但在运行时评估的内在函数;以及在运行时评估的标准库函数。您看到的确切输出将取决于每个为测试输入生成的输出,和每次比较使用哪个输出(可能取决于优化设置)。
      【解决方案4】:

      如果字符串不相等,strcmp 返回&lt; 0。
      它表示第二个字符串对于字符串中不匹配的第一个字符具有更高的值。确切的精确值是未指定。
      唯一定义的是输出是否为:

      • 零或
      • 正或
      • 否定

      【讨论】:

      • 这没有抓住问题的重点。问题是为什么 identical 输入字符串会产生不同的结果。虽然结果在形式上是“正确的”,但问题是:为什么strcmp 的行为看起来如此不确定?
      • 这很好,但这是一个现实生活中的问题。现实生活中的编译器会为相同的字符串返回不同结果的唯一原因是它是否有意随机化结果。这在某些“压力测试”编译器中可能有意义,但在“普通”编译器中则不然。所以问题是成立的。您的纯理论答案是正确的,但在现实生活中没有任何意义。或者,如果您认为有,请展示它。
      • @AndreyT “现实生活中的编译器会为相同的字符串返回不同结果的唯一原因是它有意随机化结果。” ——你错了。看我的回答。
      • “这是一个现实生活中的问题”——现实生活中的答案是:生成汇编代码并查看它。不同版本的 gcc 和不同的平台可能会有所不同。
      • @JimBalter:准确地说,这个练习是没有用的,因为即使在同一个编译器和同一个平台上,你也不能依赖相同的值,技术上(因此实际上 ) 该值可以是任何值,只要它必须是 -ve。
      猜你喜欢
      • 1970-01-01
      • 2021-12-22
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多