我们有几个中等大小的C代码库,可以接收来自具有各种经验级别的开发人员的提交.一些不那么有纪律的程序员会assert()使用副作用来声明语句,这会导致断言被禁用.例如
assert(function_that_should_always_be_called());
Run Code Online (Sandbox Code Playgroud)
我们已经使用了自己的assert()实现,但是使用NDEBUGdefine定义表达式会导致不可接受的性能下降.是否有我们可以传递的GCC扩展或标志会触发这些的编译时警告/错误?通过足够简单的控制流程,GCC应该可以确定您只调用纯函数.
Bru*_*ine 11
尽管这个问题已经收到了许多无益的非答案,但我认为它在遗留代码库的上下文中有很多优点.
想象一下,多年来积累了许多断言,但由于没有使用NDEBUG构建/测试的习惯,一些副作用已经渗透到断言中,现在你不再禁用断言了.
您可以在测试套件中打开NDEBUG并检测一些测试失败,但将测试失败与"有效"断言联系起来并不是一件容易的事,因为它可能距离您检测到失败的位置非常远.即使是具有良好覆盖范围的测试套件也不可信任完整.
您可以对代码中的所有断言进行代码审查,但这可能会导致很多工作,并且容易出现人为错误.如果某些静态分析已经消除了可以证明没有出现副作用的所有断言,并且您只需要调查那些不能保证缺席的情况,那将会好得多.
以下是如何使用编译器的优化器进行此类静态分析的方法.假设您组织以通过以下方式替换assert宏的定义:
extern int not_supposed_to_survive;
#define assert(expr) ((void)(not_supposed_to_survive || (expr)))
Run Code Online (Sandbox Code Playgroud)
如果expr有任何副作用,效果的执行是以全局变量的值为条件的not_supposed_to_survive.但是如果expr没有任何副作用,全局变量的值无关紧要(注意expr结果被丢弃).一个好的优化器知道这一点,并将消除全局变量的负载not_supposed_to_survive,因此消除变量的名称.
如果我们的程序不包含符号的定义not_supposed_to_survive,我们将在没有消除负载时得到链接错误,我们可以使用它来检测可能有效的断言.
例如,使用gcc 4.8:
int g;
int foo() { return ++g; }
int main() {
assert(foo());
return 0;
}
gcc -O2 assert_effect.c
/tmp/ccunynya.o: In function `main':
assert_effect.c:(.text.startup+0x2): undefined reference to `not_supposed_to_survive'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)
编译器帮我找到了一个可疑的断言!另一方面,如果我替换++g为g+1,链接错误消失,我不必调查.实际上,这种说法是无害的.
当然,可证明无副作用的概念受到优化器"可以看到"的限制.为了更精确的分析,我建议使用链接时优化(gcc -flto)来跨编译单元进行分析.
更新:我使用gcc 5.3将其应用于现实生活中的C++代码库.要使用链接时优化,您实际上可以使用gcc -flto -g编译器/链接器(编译器/链接器上的-g选项来获取链接错误的行参考),gcc-ar以及gcc-ranlib作为任何静态库的归档器/索引器.
这种设置可以极大地减少我必须调查的断言数量.凭借最小的人力,我能够将断言清理干净.我仍然需要手动拒绝的误报是由于:
另外,我还会得到一些确实含有副作用的断言,但它们是无害的或不重要的,例如:
我不确定这对于您所描述的应用程序是否足够,但 cppcheck 会查找“assertWithSideEffect”: http ://cppcheck.sourceforge.net/devinfo/doxyoutput/checkassert_8cpp_source.html
编译时消息如下所示: [assertWithSideEffect] myFile.cpp:42: warning: Non-pure function: 'myFunction' is called inside assert 语句。断言语句已从发布版本中删除,因此断言语句内的代码不会被执行。如果发布版本中也需要该代码,则这是一个错误。
“Cppcheck 是 C/C++ 代码的静态分析工具。与 C/C++ 编译器和许多其他分析工具不同,它不会检测代码中的语法错误。Cppcheck 主要检测编译器通常检测不到的错误类型。目标是只检测代码中的真正错误(即误报为零)。” http://cppcheck.sourceforge.net/
通过足够简单的控制流,GCC 应该可以确定您仅调用纯函数。
如果它不是一个足够简单的控制流,它如何知道它是否是纯粹的?
像这样的事情可能是你最好的选择:
#ifdef NDEBUG
#define assert(s) do { (s); } while(false)
#else
// ...
#endif
Run Code Online (Sandbox Code Playgroud)
将编译出几个表达式,包括带有__attribute__((pure)).
最合乎逻辑的解决方案是检查代码并修复错误。
| 归档时间: |
|
| 查看次数: |
2843 次 |
| 最近记录: |