首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >内联vararg函数

内联vararg函数
EN

Stack Overflow用户
提问于 2014-08-25 16:39:00
回答 2查看 3.9K关注 0票数 19

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

例如,编译以下小程序:

代码语言:javascript
复制
#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向上内联该函数。

我怀疑这与用于实现varargs的巫毒魔法有关,但这通常是如何实现的对我来说是一个谜。有没有人能告诉我编译器通常是如何实现vararg函数的,为什么这似乎会阻止内联?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-08-25 16:52:46

至少在x86-64上,var_args的传递是相当复杂的(由于在寄存器中传递参数)。其他架构可能没有那么复杂,但很少是微不足道的。特别是,可能需要在获取每个参数时引用堆栈帧或帧指针。这类规则可以很好地阻止编译器内联函数。

x86-64的代码包括将所有整数参数和8个sse寄存器推入堆栈。

这是使用Clang编译的原始代码中的函数:

代码语言:javascript
复制
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的评论:

代码语言:javascript
复制
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中,这要简单得多:

代码语言:javascript
复制
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副本会使它变得非常庞大(有各种其他缺点,包括由于编译器后端的一些糟糕的算法选择而导致最终代码生成时间更长),所以我们不得不添加代码来阻止编译器这样做……

票数 11
EN

Stack Overflow用户

发布于 2014-08-25 18:04:14

我并不认为内联varargs函数是可能的,除非在最微不足道的情况下。

没有参数的varargs函数,或者不访问其任何参数的varargs函数,或者只访问变量1之前的固定参数的varargs函数,可以通过将其重写为不使用varargs的等效函数来内联。这是一个微不足道的例子。

varargs函数通过执行由va_startva_arg宏生成的代码来访问其可变参数,这两个宏以某种方式依赖于在内存中布局的参数。仅仅为了消除函数调用的开销而执行内联的编译器仍然需要创建数据结构来支持这些宏。试图删除所有函数调用机制的编译器也必须分析和优化这些宏。而且,如果变量函数调用另一个函数,并将va_list作为参数传递,它仍然会失败。

对于第二种情况,我看不到可行的途径。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25482031

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档