代码之家  ›  专栏  ›  技术社区  ›  Artefacto

printf:如何解释损坏的结果

  •  1
  • Artefacto  · 技术社区  · 14 年前
    #include <stdio.h>
    
    int main(void)
    {
            double resd = 0.000116;
            long long resi = 0;
    
            printf("%lld %f %lld %f\n", resd, resd, resi, resi);
            return 0;
    }
    

    给出(Linux、GCC、X64)

    0 0.000116 0 0.000116
                 ^^^^^^^^ odd, since the memory for resi is zeroed
    

    实际上,用g++编译它会给出随机结果,而不是第二个0。

    我知道我给了无效的说明符 printf 它会触发 未指定的 未定义的行为,但我想知道为什么会发生这种特定的腐败,因为 long long double 尺寸相同。

    3 回复  |  直到 14 年前
        1
  •  5
  •   Carl Norum    14 年前

    我得到的结果和你在我的机器上得到的一样(mac os x,所以amd/linux abi)。浮点参数在xmm寄存器中传递,整数参数在整数寄存器中传递。什么时候? printf 抓住他们用 va_arg ,当它看到 %f 格式化,当它看到 %lld . 下面是编译后的程序的反汇编( -O0 )在我的机器上:

     1 _main:
     2   pushq   %rbp
     3   movq    %rsp,%rbp
     4   subq    $0x20,%rsp
     5   movq    $0x3f1e68a0d349be90,%rax
     6   move    %rax,0xf8(%rbp)
     7   movq    $0x00000000,0xf0(%rbp)
     8   movq    0xf0(%rbp),%rdx
     9   movq    0xf0(%rbp),%rsi
    10   movsd   0xf8(%rbp),%xmm0
    11   movq    0xf8(%rbp),%rax
    12   movapd  %xmm0,%xmm1
    13   movq    %rax,0xe8(%rbp)
    14   movsd   0xe8(%rbp),%xmm0
    15   lea     0x0000001d(%rip),%rdi
    16   movl    $0x00000002,%eax
    17   callq   0x100000f22    ; symbol stub for: _printf
    18   movl    $0x00000000,%eax
    19   leave
    20   ret
    

    在那里你可以看到发生了什么-格式字符串被传入 %rdi ,然后按以下顺序传递参数: %xmm0 , %xmm1 , %rsi %rdx . 什么时候? 普林特 获取它们,它以不同的顺序(格式字符串中指定的顺序)将它们弹出。这意味着它会弹出: %RSI , %xMM0 , %RDX , %XMM1 给出你看到的结果。这个 2 在里面 %eax 指示传递的浮点参数的数目。

    编辑:

    这里有一个优化版本-在这种情况下,较短的代码可能更容易理解。解释和上面一样,但是有一点小样板噪音。浮点值由 movsd 在第4行。

     1 _main:
     2    pushq   %rbp
     3    movq    %rsp,%rbp
     4    movsd   0x00000038(%rip),%xmm0
     5    xorl    %edx,%edx
     6    xorl    %esi,%esi
     7    movaps  %xmm0,%xmm1
     8    leaq    0x00000018(%rip),%rdi
     9    movb    $0x02,%al
    10    callq   0x100000f18   ; symbol stub for: _printf
    11    xorl    %eax,%eax
    12    leave
    13    ret
    
        2
  •  10
  •   Stephen Canon    14 年前

    因为在 x86_64 C调用平台上的约定,传入前两个浮点参数 xmm0 xmm1 ,前两个整数参数在GPRS中传递。( rsi rdx 如果您使用的是Linux或OS X),无论它们出现的顺序如何。

    您很困惑,因为您期望在内存中传递参数,而不是。

        3
  •  2
  •   EboMike    14 年前
    • 第一个数字应该是一个很高的值,因为您将一个双精度数作为一个整数传入。应该是%f。
    • “resi”中“0”之后的时间段是什么?这会把它变成一个双精度数,所以你要把一个双精度数加载成一个整数。这会给你一个编译器警告。
    • 有些实现可能是基于寄存器的,因此,由于您弄乱了参数类型,它会变得混乱。

    您在哪个平台上编译?窗户?

    您是否查看了反汇编以了解它实际上推动堆栈的是什么?它是将它们推到堆栈上,还是使用寄存器?