【问题标题】:dereferencing with different types of the same size gives different results使用相同大小的不同类型取消引用会产生不同的结果
【发布时间】:2013-05-25 07:45:53
【问题描述】:

为什么用于取消引用传递给 printf 的指针的类型会影响输出,即使类型大小相同:

void test_double(void *x)
{
    double *y = x;
    uint64_t *z = x;
    printf("double/double: %lf\n", *y);
    printf("double/uint64: %lf\n", *z);
    printf("uint64/double: 0x%016llx\n", *y);
    printf("uint64/uint64: 0x%016llx\n", *z);
}

int main(int argc, char** argv)
{    
    double x = 1.0;
    test_double(&x);
    return 0;
}

输出:

double/double: 1.000000
double/uint64: 1.000000
uint64/double: 0x00007f00e17d7000
uint64/uint64: 0x3ff0000000000000

我希望最后两行都能正确打印 0x3ff0000000000000,即 IEEE754 双浮点中 1.0 的表示。

【问题讨论】:

  • 您在什么平台上使用哪个编译器? Cygwin 下的 Gcc 4.5.3 为我打印 0x3ff0..0。

标签: c printf


【解决方案1】:

这是未定义的行为。 C 语言标准规定,如果可变参数不具有格式字符串所隐含的类型,那么这就是 UB。在您的第三个打印语句中,您传递了一个double,但它需要一个uint64_t。既然是UB,什么事都有可能发生。

这个规范允许实现做一些事情,比如在堆栈上传递整数,但通过 FPU 寄存器传递浮点值,这就是我怀疑在你的测试用例中发生的事情。例如,Linux on x86 (GCC) 上的 cdecl calling convention 在 x87 伪堆栈(寄存器 ST0...ST7)上传递浮点函数参数。

如果您查看生成的程序集,您可能会发现为什么您的第三个和第四个打印语句的行为不同。在带有 Clang 4.1 的 Mac OS X 10.8.2 64 位上,我能够重现类似的结果,并且程序集看起来像这样,我已经对其进行了注释:

        .section        __TEXT,__text,regular,pure_instructions
        .globl  _test_double
        .align  4, 0x90
_test_double:                           ## @test_double
        .cfi_startproc
## BB#0:
        pushq   %rbp
Ltmp3:
        .cfi_def_cfa_offset 16
Ltmp4:
        .cfi_offset %rbp, -16
        movq    %rsp, %rbp
Ltmp5:
        .cfi_def_cfa_register %rbp
        pushq   %rbx
        pushq   %rax
Ltmp6:
        .cfi_offset %rbx, -24

    # printf("%lf", double)
        movq    %rdi, %rbx
        movsd   (%rbx), %xmm0
        leaq    L_.str(%rip), %rdi
        movb    $1, %al
        callq   _printf

    # printf("%lf", uint64_t)
        movq    (%rbx), %rsi
        leaq    L_.str1(%rip), %rdi
        xorb    %al, %al
        callq   _printf

    # printf("%llx", double)
        leaq    L_.str2(%rip), %rdi
        movsd   (%rbx), %xmm0
        movb    $1, %al
        callq   _printf

    # printf("%llx", uint64_t)
        leaq    L_.str3(%rip), %rdi
        movq    (%rbx), %rsi
        xorb    %al, %al
        addq    $8, %rsp
        popq    %rbx
        popq    %rbp
        jmp     _printf                 ## TAILCALL
        .cfi_endproc

在打印double 值的情况下,它会将参数放入SIMD %xmm0 register:

movsd   (%rbx), %xmm0

但在uint64_t 值的情况下,它通过整数寄存器%rsi 传递参数:

movq    (%rbx), %rsi

【讨论】:

  • 啊,谢谢。我不习惯 x86 组装,我的大部分组装经验都是使用更简单的 RISC 系统(没有单独的 FPU 堆栈)完成的。
  • 好答案。我认为“...第三个打印语句,你传递了一个 uint64_t,但它期待一个双精度”可能是相反的。
猜你喜欢
  • 2017-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-17
  • 2017-12-07
相关资源
最近更新 更多