编译器有什么有趣的地方?
当编译器优化遇上 UB(未定义行为),就会很有意思。我这里分享某论坛上一个有名的例子——“C++编译器证伪费马大定理”,侵删。
考虑以下程序
如果 13 行的方程在 1e9 内有解,函数就会返回,否则陷入死循环。而根据费马大定理,该方程无解。
在无优化下,程序如预期陷入死循环;而在 O2 优化下,程序却正常退出了
这是因为,编译器认为函数一定会返回,而 31 行的 return 因为前面的死循环必然执行不到,所以编译器认定程序一定会从 14 行处返回,再加上该函数没有任何副作用
于是这个函数就被优化成了这个逼样(bool 变成 int64 大概是因为返回值寄存器是 64bit)
因为 UB 的存在,编译器可能会对代码进行很多过激的优化,从而带来难以预料的错误。类似的例子网上能找到很多
update: 2024/08/27
读了一些评论,
表述欠妥。应该是,无副作用的死循环是 UB,所以编译器认定循环一定会退出,所以认为 14 行一定会执行;如此,该函数唯一的作用就成了 return false,因为其它语句没有副作用。
通过某种手段使得该循环具有副作用,比如输出、使用全局变量等,可以避免过度优化,但这是个坏主意。真正要做的应该是避免这种死循环,比如当 a b c 均达到 MAXN 就退出循环。
关于编译器“乱优化”,其实我觉得这是一把极锋利的刀,用好了很利,用不好就容易折。我记得最早 C 的委员会有一个毁誉参半的原则,大概是“信任程序员”;不清楚现在委员会是否还持有这个原则,但 g++ 似乎就非常信任我不会写出 UB,然而我本人一点信心都没有~
上面的函数没有内联。如果内联了,在 O2 下,整个程序会被直接优化成这样:
int main() {
cout << "Fermat's theorem is wrong!" << endl;
return 0;
}