为什么用于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的表示。
答案 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