不同类型相同大小的解引用会产生不同的结果。

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的表示方式。

你在什么平台上使用什么编译器?我在Cygwin下使用GCC 4.5.3输出0x3ff0..0。 - jmihalicza
1个回答

4
这是未定义行为。C语言标准规定,如果可变参数的类型与格式字符串所示的类型不符,则属于未定义行为。在第三个打印语句中,您传递了一个double,但它期望的是一个uint64_t。由于这是未定义行为,任何事情都可能发生。
此规范允许实现像通过FPU寄存器传递整数,而通过栈传递浮点值这样的操作,这正是我怀疑在您的测试用例中发生的情况。例如,在Linux x86(GCC)上使用cdecl calling convention时,浮点函数参数会通过x87伪堆栈(寄存器ST0...ST7)传递。
如果您查看生成的汇编代码,您可能会发现为什么第三个和第四个打印语句的行为不同。在Mac OS X 10.8.2 64位Clang 4.1上,我能够重现类似的结果,汇编代码看起来像这样,并进行了注释:
        .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

啊,谢谢。我不太习惯x86汇编,我的大部分汇编经验都是用更简单的RISC系统完成的(这些系统没有单独的FPU堆栈)。 - charliehorse55
好的回答。我认为“...第三个打印语句,你传递了一个uint64_t,但它期望一个double”可能是相反的。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接