至少在 x86-64 上,var_args 的传递非常复杂(由于在寄存器中传递参数)。其他架构可能没有那么复杂,但它很少是微不足道的。特别是,可能需要在获取每个参数时引用堆栈帧或帧指针。这些规则很可能会阻止编译器内联函数。
x86-64 的代码包括将所有整数参数和 8 个 sse 寄存器推入堆栈。
这是使用 Clang 编译的原始代码中的函数:
test: # @test
subq $200, %rsp
testb %al, %al
je .LBB1_2
# BB#1: # %entry
movaps %xmm0, 48(%rsp)
movaps %xmm1, 64(%rsp)
movaps %xmm2, 80(%rsp)
movaps %xmm3, 96(%rsp)
movaps %xmm4, 112(%rsp)
movaps %xmm5, 128(%rsp)
movaps %xmm6, 144(%rsp)
movaps %xmm7, 160(%rsp)
.LBB1_2: # %entry
movq %r9, 40(%rsp)
movq %r8, 32(%rsp)
movq %rcx, 24(%rsp)
movq %rdx, 16(%rsp)
movq %rsi, 8(%rsp)
leaq (%rsp), %rax
movq %rax, 192(%rsp)
leaq 208(%rsp), %rax
movq %rax, 184(%rsp)
movl $48, 180(%rsp)
movl $8, 176(%rsp)
movq stdout(%rip), %rdi
leaq 176(%rsp), %rdx
movl $.L.str, %esi
callq vfprintf
addq $200, %rsp
retq
并来自 gcc:
test.constprop.0:
.cfi_startproc
subq $216, %rsp
.cfi_def_cfa_offset 224
testb %al, %al
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
je .L2
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.L2:
leaq 224(%rsp), %rax
leaq 8(%rsp), %rdx
movl $.LC0, %esi
movq stdout(%rip), %rdi
movq %rax, 16(%rsp)
leaq 32(%rsp), %rax
movl $8, 8(%rsp)
movl $48, 12(%rsp)
movq %rax, 24(%rsp)
call vfprintf
addq $216, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
在 x86 的 clang 中,它要简单得多:
test: # @test
subl $28, %esp
leal 36(%esp), %eax
movl %eax, 24(%esp)
movl stdout, %ecx
movl %eax, 8(%esp)
movl %ecx, (%esp)
movl $.L.str, 4(%esp)
calll vfprintf
addl $28, %esp
retl
没有什么能真正阻止上述任何代码被内联,所以看起来这只是编译器编写者的一个策略决定。当然,对于类似的调用,为了printf
代码扩展的成本优化调用/返回对是毫无意义的——毕竟, printf 不是一个小的短函数。
(在过去一年的大部分时间里,我的大部分工作都是在 OpenCL 环境中实现 printf,所以我知道的远比大多数人甚至会查找格式说明符和 printf 的其他各种棘手部分要多得多)
编辑:我们使用的 OpenCL 编译器将内联调用 var_args 函数,因此可以实现这样的事情。它不会对 printf 的调用执行此操作,因为它会使代码非常臃肿,但默认情况下,我们的编译器会一直内联所有内容,无论它是什么......它确实有效,但我们发现有代码中的 2-3 个 printf 副本使其非常庞大(还有各种其他缺点,包括由于编译器后端中一些错误的算法选择导致最终代码生成需要更长的时间),因此我们不得不将代码添加到 STOP编译器这样做...