编译器是否会将常量参数转换为唯一指令来简化简单函数?

时间:2015-07-19 13:21:01

标签: c++ c gcc optimization compiler-optimization

这是我一直认为是真实的,但从未进行任何验证。考虑一个非常简单的功能:

int subtractFive(int num) {
    return num -5;
}

如果对此函数的调用使用编译时常量,例如

  getElement(5);

打开优化的编译器很可能会内联这个。然而,我不清楚的是,如果在运行时或编译时评估num-5。表达式简化是否会以这种方式通过内联函数递归递增?或者它没有超越功能?

3 个答案:

答案 0 :(得分:13)

我们可以简单地查看生成的程序集以查找。这段代码:

int subtractFive(int num) {
    return num -5;
}

int main(int argc, char *argv[]) {
  return subtractFive(argc);
}

使用g++ -O2编译产生

leal    -5(%rdi), %eax
ret

因此函数调用确实简化为单个指令。此优化技术称为inlining

当然可以使用相同的技术来查看编译器将使用多少,例如

稍微复杂一点
int subtractFive(int num) {
    return num -5;
}

int foo(int i) {
    return subtractFive(i) * 5;
}

int main(int argc, char *argv[]) {
  return foo(argc);
}

仍然被编译为

leal    -25(%rdi,%rdi,4), %eax
ret

所以这里的两个函数都在编译时被删除了。如果在编译时已知foo的输入,则函数调用(在这种情况下)将在编译时(Live)简单地替换为结果常量。

编译器还可以将此内联与常量折叠组合在一起,如果所有参数都是编译时常量,则用完全求值的结果替换函数调用。例如,

int subtractFive(int num) {
    return num -5;
}

int foo(int i) {
    return subtractFive(i) * 5;
}

int main() {
  return foo(7);
}

编译到

mov     eax, 10
ret

相当于

int main () {
    return 10;
}

编译器将始终在认为这是一个好主意的情况下执行此操作,并且(通常)在优化此级别的代码方面(通常)更好。

答案 1 :(得分:3)

做一点测试很容易;请考虑以下

int foo(int);
int bar(int x) { return x-5; }
int baz() { return foo(bar(5)); }

使用g++ -O3编译函数baz的asm输出是

xorl    %edi, %edi
jmp _Z3fooi

此代码在第一个参数中加载0,然后跳转到foo的代码。因此bar中的代码完全消失了,传递给foo的值的计算已在编译时完成。

此外,返回调用函数的值只是跳转到函数代码(这称为“尾调用优化”)。

答案 2 :(得分:1)

智能编译器将在编译时对此进行评估,并将替换getElement(5),因为它永远不会有不同的结果。没有变量被认为是易变的。