C 编译器对只包含一个函数调用的函数做什么

C 编译器对只包含一个函数调用的函数做什么,c,C,正如标题所示,如果我有: void a(uint8_t i) { b(i, 0); } 编译器是否能够将对a(i)的调用替换为b(i,0) 此外,在任何一种情况下,下列做法是否被视为替代上述做法的良好做法: #define a(i) b(i, 0) 编译器很可能会优化此代码,并使其成为: 因此像a(i)这样的调用实际上将被b(i,0)所取代,这是非常容易测试的。如果对a的调用在同一个编译单元中,大多数编译器都会对其进行优化。让我们看看会发生什么: $ cat > foo.c voi

正如标题所示,如果我有:

void a(uint8_t i) {
  b(i, 0);
}
编译器是否能够将对a(i)的调用替换为b(i,0)

此外,在任何一种情况下,下列做法是否被视为替代上述做法的良好做法:

#define a(i) b(i, 0)

编译器很可能会优化此代码,并使其成为:


因此像
a(i)
这样的调用实际上将被
b(i,0)
所取代,这是非常容易测试的。如果对
a
的调用在同一个编译单元中,大多数编译器都会对其进行优化。让我们看看会发生什么:

$ cat > foo.c
void b(int, int);

void
a(int a)
{
        b(a, 0);
}

void
foo(void)
{
        a(17);
}
然后通过一些基本优化将其编译为汇编程序(我添加了省略帧指针以创建更干净的输出,您可以验证在没有该标志的情况下是否会发生完全相同的事情):

然后看看输出(我清理了它并保留了代码,在生成的汇编程序中有很多注释与此无关):

因此我们可以在这里看到编译器首先生成了一个调用
b
的普通函数
a
(除了它的尾部调用经过优化,所以它是jmp而不是调用)。然后当编译
foo
而不是调用
a
时,它只是将其内联


我在本例中使用的编译器是相对较旧的gcc版本,我还检查了clang是否做了完全相同的事情。这是相当标准的优化,只要编译器进行任何内联,像这样的简单函数将始终内联。

这取决于几件事,其中最重要的是选择工具链(编译器、链接器等)和优化设置

如果编译器可以看到
a()
,而不仅仅是一个声明,那么它可能会选择内联
a()
。编译器不需要这样做,但根据优化设置和编译器本身的实现质量,它可能会这样做。然而,对于现代编译器来说,您的案例是一种相当常见且直接的优化

如果函数未声明为
static
(这非常简单地使其成为特定编译单元的本地函数),那么大多数编译器仍将在目标文件中保留函数
a()
的定义,因此它可以与其他目标文件(对于其他编译单元)链接。即使它选择在定义它的编译单元内内联函数调用

如果函数被声明为
inline
(并且编译器具有定义的可见性),则实际上同样适用<代码>内联是标准允许编译器忽略的提示,无论程序员多么坚决。实际上,现代编译器在决定内联哪些函数方面往往比程序员做得更好

如果您有存储
a()
地址的代码(例如,在指向函数的指针中),编译器可能会选择不内联它

即使编译器没有内联函数,智能链接器也可能选择(实际上)内联函数。然而,大多数C实现都使用传统的哑链接器作为工具链的一部分,因此这种类型的链接时间优化在实践中是不可能的

即使链接器没有,某些虚拟机主机环境也可能选择在运行时内联。这对于一个C程序来说是非常不寻常的,但并不超出可能的范围

就我个人而言,我不会为此担心。无论编译器是否进行这种类型的优化,都不会有明显的差异(例如,在程序性能、大小等方面),除非您拥有大量这样的函数

我不会使用宏。如果您真的不想在使用
b()
时键入
,0
,那么只需编写函数
a()
,让编译器担心它。只有当性能度量和分析显示函数
a()
是性能热点时,才尝试手动进一步优化。可能不会


或使用C++,并声明函数<代码>()(代码)>第二个参数的默认值<代码> 0 /代码>;p> 前者是“尾部呼叫消除”,这是一种已知的优化策略;后者是不行的。如果

a()
的定义在使用点可见,或者如果构建系统被指示执行链接时间或整个程序优化,那么它将做到这一点(以及更多)。(这意味着对于标准构建系统,
a()
如果驻留在不同的翻译单元中,则不能内联。)它不会内联,因为“a”必须在全局符号表中导出,理论上可能从“外部”@M.M引用它(没有考虑到这一点)。你知道一个编译器,它真的可以做到这一点吗?@Ctx gcc就是这样做的。叮当声也是。测试起来非常简单。@Ctx AFAIK内联和外部链接并不是相互排斥的。编译器可以试探性地内联任何看似合适的函数。Gcc在O2优化级别上做到了这一点。谢谢,我真的没有想到这种可能性。这已经不是什么不寻常的事情了。gcc有-flto(),如果翻译单元被提供给单个gcc调用,则会跨这些转换单元进行优化。
$ cat > foo.c
void b(int, int);

void
a(int a)
{
        b(a, 0);
}

void
foo(void)
{
        a(17);
}
$ cc -fomit-frame-pointer -S -O2 foo.c
$ cat foo.s
a:
    xorl    %esi, %esi
    jmp b
foo:
    xorl    %esi, %esi
    movl    $17, %edi
    jmp b