采取以下代码:
#include <stdbool.h>
bool global;
bool foo(void) {
if (global) {
global = false;
return true;
}
return false;
}
此代码理论上等于以下代码吗?
#include <stdbool.h>
bool global;
bool baz(void) {
bool tmp = global;
global = false;
return tmp;
}
我倾向于认为
foo
和 baz
在理论上是等价的:global
不是易失性的,并且没有其他因素迫使编译器考虑多线程(例如,像 atomic_bool
这样的东西)。所以我认为如果一个分支比商店贵得多,那么编译器会选择使用 baz
解决方案而不是 foo
解决方案。但我无法让编译器实际执行此操作,使用带有 -std=gnu2x -march=rv32if -mabi=ilp32f -O3 -mbranch-cost=2000
的 GCC 13.2.0 RISC-V 编译器仍然会导致 foo
。另一方面,我也无法让编译器将baz
变成foo
。
那么我错了吗?
foo
和baz
之间有理论上的区别吗?或者这只是 GCC(RISC-V?)不采取的优化?
修改对象就是 C 正式所说的“副作用”(c17 5.1.2.3),编译器不允许优化“需要的副作用”,但“需要”是相当主观的...... C 标准并没有真正说明是否允许编译器添加原始代码中不存在的新副作用。它只是说不允许编译器影响程序的结果 - “可观察的行为”。访问
volatile
对象会影响可观察的行为,但这并不适用于此处。
用后者替换前一个函数是至少程序员可以做到的手动优化。我不确定为什么 gcc 和 clang 都没有优化代码来删除分支,但我们有时倾向于高估优化器 - 它们不会神奇地工作。但是,如果您将代码更改为
global = false;
global = false;
那么只有一种副作用是“需要”的,并且可以删除一种,因为该对象不是
volatile
并且写入普通变量除了副作用之外没有特殊含义。这里的额外写入确实得到了优化。
请注意,编译器可能不知道如何在程序的其他地方使用外部链接变量global
,这可能会限制可能的优化。