gpt4 book ai didi

c++ - 可变参数函数的内联

转载 作者:IT老高 更新时间:2023-10-28 22:04:08 25 4
gpt4 key购买 nike

在玩优化设置时,我注意到一个有趣的现象:采用可变数量参数的函数 (...) 似乎从未内联。 (显然这种行为是特定于编译器的,但我已经在几个不同的系统上进行了测试。)

例如编译如下小程序:

#include <stdarg.h>
#include <stdio.h>

static inline void test(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
}

int main()
{
test("Hello %s\n", "world");
return 0;
}

似乎总是会导致(可能被损坏)test 符号出现在生成的可执行文件中(在 MacOS 和 Linux 上的 C 和 C++ 模式下使用 Clang 和 GCC 进行测试)。如果修改 test() 的签名以获取传递给 printf() 的纯字符串,则该函数从 -O1 内联正如您所期望的那样,两个编译器都向上。

我怀疑这与用于实现可变参数的巫毒魔法有关,但是这通常是如何完成的对我来说是个谜。谁能告诉我编译器通常如何实现可变参数函数,以及为什么这似乎阻止了内联?

最佳答案

至少在 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编译器这样做...

关于c++ - 可变参数函数的内联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25482031/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com