我想了解如果删除本地中间变量是否可以导致更优化的代码。请考虑以下MWE,特别注意两个函数f
和g
:
struct A {
double d;
};
struct B {
double s;
};
struct C {
A a;
B b;
};
A geta();
B getb();
C f() {
const A a = geta();
const B b = getb();
C c;
c.a = a;
c.b = b;
return c;
}
C g() {
C c;
c.a = geta();
c.b = getb();
return c;
}
f
和g
都调用geta()
和getb()
来填充一个C
类的实例,然后返回该实例,但是f
使用两个本地中间变量来存储geta()
和getb()
的返回值,而g
直接将返回值分配给c
的成员。
使用gcc -O3
,版本9.2编译,两个函数f
和g
的二进制代码完全相同。然而,向A
或B
类添加另一个变量会导致二进制代码不同。特别是,f
的二进制代码有一些额外的指令。对于clang v8.0.0也是如此,使用-O3
标志。
这里发生了什么?为什么编译器不能在A
或B
变得更加复杂时优化掉f
的本地中间变量?f
和g
的代码不等价吗?
此外,使用/O2
标志的MSVC v19.22的行为也不相同:Microsoft的编译器在第一种情况下已经具有不同的二进制代码,即A
和B
都由单个double
组成。
我正在使用Godbolt:您可以在这里找到产生不同二进制代码的代码。
A
和B
调用函数,C
(c
)分配成员并返回值。根据您的片段很难确定您认为应该优化掉什么?进一步选择要取出的变量是单个变量。如果您有一个对象和该对象的几个成员(例如.a
和.b
什么也不做),它们仍然不太可能被优化掉,因为它们不是独立的变量。 - David C. RankingetA()
和getB()
提供简单的定义,那么f()
和g()
的汇编代码将是相同的。https://godbolt.org/z/lOjAC- - SebiA
添加微不足道的自定义赋值运算符,它将在gcc
中使f()
和g()
的机器代码再次相同... :D :D :D ...(要添加到struct A
中的代码:A&operator =(const A&t){return * this;}
....似乎默认赋值运算符正在执行某些“额外”的操作,这会防止折叠,或者仅仅是因为该运算符不是显式的,而gcc必须用默认运算符填补空白,从而防止优化器折叠它。....但是对于您的问题,一般答案实际上非常简单:“为什么不呢?”...这是优化器,而不是“找到最佳解决方案的器”。 - Ped7g