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

时间:2015-12-15 10:15:47

标签: c

如标题所示,如果我有以下情况会发生什么:

void a(uint8_t i) {
  b(i, 0);
}

编译器能否用b(i,0)替换对(i)的调用?

此外,在任何一种情况下,以下都将被视为取代上述内容的良好做法:

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

3 个答案:

答案 0 :(得分:3)

这很容易测试。如果对a的调用在同一编译单元中,则大多数编译器都会对其进行优化。让我们看看会发生什么:

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

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

void
foo(void)
{
        a(17);
}

然后使用一些基本的优化将它编译为汇编程序(我添加了omit-frame-pointer来创建更清晰的输出,你可以验证没有那个标志会发生完全相同的事情):

$ 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

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

我在这种情况下使用的编译器是一个相对较旧版本的gcc,我也检查过clang做的完全相同。这是非常标准的优化,只要编译器执行任何内联,就会总是内联这样的简单函数。

答案 1 :(得分:2)

这取决于一些事情,尤其是您选择的工具链(编译器,链接器等)和优化设置。

如果编译器可以看到a()的定义 - 而不仅仅是声明 - 它可能会选择内联a()。编译器不需要这样做,但是,根据优化设置和编译器本身的实现质量,它可能会。但是,对于现代编译器来说,这是一个相当普遍和直接的优化。

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

如果函数声明为inline(并且编译器具有定义的可见性),则实际应用该函数。 inline是标准允许编译器忽略的提示,无论程序员多么坚定。在实践中,现代编译器通常可以更好地决定内联函数而不是程序员。

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

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

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

就个人而言,我不担心。除非您拥有真正大量的此类函数,否则编译器是否会执行此类优化,几乎没有可观察到的差异(例如程序性能,大小等)。

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

或者,使用C ++,并为第二个参数声明函数b(),默认值为0。 ;)

答案 2 :(得分:1)

编译器很可能会优化此代码,并将其设为inline function

inline void a(uint8_t i) {
  b(i, 0);
}

a(i)之类的调用确实会被b(i, 0)

取代