这是未定义行为。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