使用相同大小的不同类型进行解除引用会产生不同的结果

时间:2013-05-24 23:18:44

标签: c printf

为什么用于derefernce指针传递给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的表示。

1 个答案:

答案 0 :(得分:4)

这是未定义的行为。 C语言标准说如果可变参数不具有格式字符串隐含的类型,那么就是UB。在您的第三个打印声明中,您传递的是double,但它预计会有uint64_t。因为它是UB,所以任何事情都可能发生。

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

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

        .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