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

标签 c++ c variadic-functions inline-functions

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

例如编译如下小程序:

#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/

相关文章:

c - 如何在 Linux 上的 C 中包含来自多个目录的文件?

c - 还有其他方法可以将指针地址交换为交换值吗?

go - slice slice 的拆包

c++ - 使用 double 值的货币格式

c++ - 有没有办法使用 clang-format 来做 "indentation only"?

c++ - 如何在 C++ 中使用带有 'do while' 循环的计数器?

c++ - 如何使用 c-ares 将 IP 解析为主机?

C 程序模拟命令行提示符

java - generic varargs 的编译器警告的解决方案

c - 验证可变长度参数