2012-05-15 26 views
10

我们有几个中等大小的C代码库,它们接收来自具有各种经验级别的开发人员的提交。一些不太严谨的程序员提交了assert()声明,其副作用会导致声明被禁用。例如。捕捉带有副作用的断言()

assert(function_that_should_always_be_called()); 

我们已经用我们自己的assert()实现,但评估与界定NDEBUG表达将导致不可接受的性能下降。是否有GCC扩展或我们可以通过的标志会触发编译时警告/错误?通过简单的控制流程,GCC应该可以确定您只是调用纯函数。

+0

不,GCC不检查功能是否有副作用。 –

+4

也许需要在提交之前进行代码审查。 –

+0

为了澄清我的问题,我们没有资源对这些存储库中的某些存储库执行手动代码审查。对于我们的高优先级,我们做代码审查。我一直在寻找自动化的东西,这将使我们不得不为这些微不足道的错误承诺提供回扣。 – Matthew

回答

3

使用简单的控制流程,GCC应该可以确定您只是调用纯函数。

如果它不是一个简单的控制流程,它将如何知道它是否纯净?


像这样的东西可能是你最好的选择:

#ifdef NDEBUG 
#define assert(s) do { (s); } while(false) 
#else 
// ... 
#endif 

几个表情会编出来,包括__attribute__((pure))功能。

最合乎逻辑的解决方案是仅检查您的代码并修复错误。

+1

同意 - 对于assert()'的正确使用,其中表达式没有副作用 - 只要启用了优化,编译器将能够删除代码。转换为'(void)'在这里很有用,因为它可能会阻止编译器警告没有副作用的语句。 – caf

+0

很明显,更复杂的控制流和类似递归的东西可以把它变成暂停问题,但大多数断言都是相对简单的。我正在设想一些超时或最大通话深度限制的检查。至于这个建议,正如我在问题中所说的那样,我特别不希望在定义NDEBUG时评估所有断言。 – Matthew

+0

@Mthethew“我正在设想一些超时或最大通话深度限制的检查。” - 与编译时检查非纯函数有关的是什么?在绝望中,你无法理解海湾合作委员会的一些特征,或者对手册的细读......显而易见并不存在。 –

4

即使GCC可以可靠地检测到纯粹的计算(这将需要解决暂停问题),但一个标记必须具有额外的魔力才能注意到非纯计算作为参数传递到您自己生成的断言宏。扩展也无法帮助 - 它究竟应该做什么?

解决你的问题是

  1. 服务能力的开发者。
  2. 教育您的开发人员如何使用断言(除其他外)。
  3. 做代码评论。
  4. 对可交付版本进行所有测试 - 如果断言在可交付成果中关闭,则断言(function_that_should_always_be_called())与简单地省略function_that_should_always_be_called()相同,这是应该在测试中发现的明显错误。
+3

我认为这些“解决方案”是无益的。但更重要的是,因为纯粹的计算不能被检测到,所以并不意味着消除纯粹的计算并不是非常有用。 http://stackoverflow.com/a/35294344/6918 –

+0

什么是没有帮助的:a)恐吓报价b)陈述个人意见,这些解决方案是没有帮助的,没有反驳提供c)明目张胆的稻草人 - 没有人说检测大多数纯粹的计算是没有用的d)通过downvotes驱动。布鲁诺的答案提供了一个聪明的技巧,它应该被接受,但可以提供这样的答案而不是一个混蛋。 –

5

尽管这个问题已经收到许多无益的非答案,但我认为它在遗留代码库的背景下有很多优点。

想象一下,多年来积累了许多断言,但由于没有使用NDEBUG进行构建/测试的习惯,因此一些副作用已经流入断言中,现在您不敢断言断言了。

您可以打开NDEBUG并检测测试套件中的某些测试失败,但将测试失败与“有效”断言关联起来并不容易,因为它可能距离检测失败的地方非常远。即使是具有良好覆盖范围的测试套件也不能完全信任。

您可以对代码中的所有断言进行代码审查,但这可能是很多工作并容易出现人为错误。如果某些静态分析已经可以消除所有可以证明不会出现副作用的断言,并且只需要调查那些不能保证其缺席的情况,那将会好得多。

下面介绍如何使用编译器的优化器进行这种静态分析。假设你通过组织来代替assert宏的定义:

extern int not_supposed_to_survive; 
#define assert(expr) ((void)(not_supposed_to_survive || (expr))) 

如果expr有任何副作用,效果的执行是以全局变量not_supposed_to_survive的价值条件。但是如果expr没有任何副作用,那么全局变量的值无关紧要(注意expr结果将被丢弃)。 好的优化器知道这一点,并将消除全局变量not_supposed_to_survive的负载,因此该变量的名称。

如果我们的程序没有包含符号not_supposed_to_survive的定义,那么当负载没有被消除时,我们会得到一个链接错误,我们可以使用它来检测潜在的有效断言。

E.g.与海湾合作委员会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 

编译器帮助我找到一个可疑的断言!另一方面,如果我将++g替换为g+1,则链接错误消失,我不必调查。事实上,这个断言是保证无害的。

当然,可证明无副作用的概念受限于优化程序“可以看到”的内容。为了更精确的分析,我建议使用链接时间优化(gcc -flto)来分析各个编译单元。

更新:我将此应用于使用gcc 5.3的现实生活C++代码库。要使用链接时优化,基本上使用gcc -flto -g作为编译器/链接器(编译器/链接器上的-g选项以获取链接错误的行参考),并使用gcc-argcc-ranlib作为任何静态库的存档器/索引器。

这种设置可以极大地减少我不得不调查的断言的数量。用最少的人力,我能够使断言变得清晰。误报,我仍然不得不手动调低的原因是:

  • 虚函数调用
  • 非平凡循环/递归(其中优化不能证明他们是有限的)

此外,我还会得到一些确实含有副作用的断言,但它们无害或不重要,例如:

  • 包含日志语句的函数
  • 功能是缓存的结果(S)
0

我不知道它是否会够你所描述的应用程序,但cppcheck查找“assertWithSideEffect” S: http://cppcheck.sourceforge.net/devinfo/doxyoutput/checkassert_8cpp_source.html

这里什么编译时消息看起来像: [assertWithSideEffect] myFile.cpp:42:警告:非纯函数:'myFunction'在assert语句中调用。断言语句从发布版本中删除,所以assert语句中的代码不会被执行。如果代码在发布版本中也需要,这是一个错误。 “Cppcheck是一个用于C/C++代码的静态分析工具,与C/C++编译器和许多其他分析工具不同,它不检测代码中的语法错误。Cppcheck主要检测编译器通常不会检测到的错误类型检测。目标是仅检测代码中的实际错误(即,具有零误报)。“ http://cppcheck.sourceforge.net/