【问题标题】:printf: how to explain corrupted resultprintf:如何解释损坏的结果
【发布时间】:2010-09-08 17:22:36
【问题描述】:
#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 ^^^^^^^^ 奇数,因为 resi 的内存归零

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

我知道我给printf 提供了无效的说明符,它会触发unspecified 未定义的行为,但我想知道为什么会发生这种特定的损坏,因为long longdouble 具有相同的大小。

【问题讨论】:

  • it triggers unspecified behavior ... nuff 说:P
  • @pmg:行为是未定义,并非未指定 (7.19.6.1.9)。有区别。
  • @Martin:标准不能解释,但还是可以解释的;行为是未定义的,不是不确定的。这是一个真正的问题,与 C 语言无关。
  • @Martin 我添加了标签调用约定和 x64。这实际上就是这个问题的意义所在。

标签: c linux 64-bit printf calling-convention


【解决方案1】:

这是因为根据您平台上的x86_64 C 调用约定,前两个浮点参数在xmm0xmm1 中传递,前两个整数参数在GPR 中传递(rsirdx 如果您使用的是 Linux 或 OS X),无论它们出现的顺序如何。

您感到困惑,因为您期望参数在内存中传递;他们不是。

【讨论】:

    【解决方案2】:

    我得到的结果与你在我的机器上得到的结果相同(Mac OS X,所以 AMD/Linux ABI)。浮点参数在 XMM 寄存器中传递,整数参数在整数寄存器中传递。当printf 使用va_arg 抓取它们时,它会在看到%f 格式时从XMM 中提取,当看到%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。当printf 获取它们,它会以不同的顺序(格式字符串中指定的顺序)弹出它们。这意味着它会弹出它们:%rsi%xmm0%rdx%xmm1,给出你看到的结果。 %eax中的2是表示传递的浮点参数个数。

    编辑:

    这是一个优化版本 - 在这种情况下,较短的代码可能更容易理解。解释与上面相同,但样板噪音要少一些。浮点值由第 4 行的movsd 加载。

     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
    

    【讨论】:

      【解决方案3】:
      • 第一个数字应该是一个高值,因为您将双精度数作为整数传递。应该是 %f。
      • “resi”中“0”之后的句点是什么?这会将它变成一个双精度数,因此您尝试将双精度数加载到一个整数中。这应该会给你一个编译器警告。
      • 某些实现可能是基于寄存器的,因此由于您弄乱了参数类型,它会变得混乱。

      你在什么平台上编译?窗户?

      您是否查看了反汇编以了解它实际推送到堆栈上的内容?它甚至会将它们压入堆栈,还是使用寄存器?

      【讨论】:

      • %f 格式说明符到 printf 用于双精度,而不是浮点数(这与 scanf 不同); va_arg 函数的浮点参数被隐式提升为 double。 %Lf 是长双倍。
      • 我不小心写了句号,然后又把它删掉了(反正没关系)。这是用 gcc 编译的 Linux amd64。 (反对票不是我的)。
      猜你喜欢
      • 2013-10-24
      • 2014-04-20
      • 2010-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-10
      相关资源
      最近更新 更多