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