3

为什么用于取消引用传递给 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 的表示。

4

1 回答 1

4

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

该规范允许实现执行诸如在堆栈上传递整数但通过 FPU 寄存器传递浮点值之类的事情,这就是我怀疑在您的测试用例中发生的事情。例如,Linux on x86 (GCC) 上的cdecl 调用约定在 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寄存器

movsd   (%rbx), %xmm0

但是对于一个uint64_t值,它通过整数寄存器传递参数%rsi

movq    (%rbx), %rsi
于 2013-05-24T23:28:55.310 回答