我有一个宏在我的代码中使用,在调试模式下:
#define contract(condition) \
if (!(condition)) \
throw exception("a contract has been violated");
Run Code Online (Sandbox Code Playgroud)
...但在发布模式下:
#define contract(condition) \
if (!(condition)) \
__builtin_unreachable();
Run Code Online (Sandbox Code Playgroud)
这样做的原因assert()是,在发布版本中,由于UB传播,编译器可以大量优化代码.
例如,使用以下代码进行测试:
int foo(int i) {
contract(i == 1);
return i;
}
// ...
foo(0);
Run Code Online (Sandbox Code Playgroud)
...在调试模式下抛出异常,但return 1;在释放模式下为无条件生成程序集:
foo(int):
mov eax, 1
ret
Run Code Online (Sandbox Code Playgroud)
条件以及依赖它的一切都已经过优化.
我的问题出现在更复杂的条件下.当编译器无法证明条件没有副作用时,它不会将其优化出来,与不使用合同相比,这是一个严重的惩罚.
有没有办法表明合同中的条件没有副作用,所以总是优化出来?
考虑一个功能
void f() {
assert(condition);
...
}
Run Code Online (Sandbox Code Playgroud)
在调试模式下,如果启用了断言,则编译器可以自由地假设condition保持,因为如果没有,则不会执行剩余的代码.
但是,在发布模式下,我相信编译器只会看到
void f() {
...
}
Run Code Online (Sandbox Code Playgroud)
而且不能再假设了condition.
是否有任何编译器指令或静态断言技巧让编译器了解某些不变量?
上下文:在这个答案中,我了解到 gcc__builtin_unreachable()可能会对性能产生一些令人惊讶的影响,因为看起来如下:
if(condition) __builtin_unreachable();
Run Code Online (Sandbox Code Playgroud)
被完全剥离,只要condition能保证没有任何副作用,就可以用作优化提示。
因此,我对此的立即反应是,我应该创建以下宏,并在我通常使用的任何地方都使用它assert(),因为在 an 内诱导代码的副作用assert()首先将是一个主要错误:
// TODO: add handling of other compilers as appropriate.
#if defined(__GNUC__) && defined(NDEBUG)
#define my_assert(condition) \
if(!(condition)) __builtin_unreachable()
#else
#define my_assert(condition) assert(condition)
#endif
Run Code Online (Sandbox Code Playgroud)
从标准的角度来看,这将在正常和构建之间创建功能划分NDEBUG,您可以提出一个论据,将该宏排除在assert(). 然而,由于无论如何,在断言失败的情况下,我的代码在功能上都会死在水中,因此从行为的角度来看,它是完全等效的。
所以我的问题是:任何人都可以想出一个不这样做的理由(除了涉及大量间接的断言之外)?
在你问之前,是的,我已经检查过 gcc 的行为是在构建中无效地放弃断言NDEBUG。