检测未定义行为的C ++实现?

C ++中的大量操作导致了未定义的行为,规范完全忽略了程序的行为应该是什么,并允许发生任何事情。 正因为如此,各种各样的情况下,人们的代码可以在debugging但不是释放模式下编译,或者在一个看起来不相关的变化发生之前,或者在一个机器上运行,而不是在另一个机器上运行。

我的问题是是否有一个实用程序查看C ++代码的执行情况,并标记程序调用未定义行为的所有实例。 虽然我们有像valgrind和检查过的STL实现这样的工具是很好的,但是它们并不像我想的那么强大 – 例如,如果你丢弃了仍然分配的内存,valgrind可能会产生错误的否定,例如,检查STL实现将不会捕获通过基类指针删除。

这个工具是否存在? 或者它甚至会有用吗?

编辑 :我知道,一般静态检查是否C ++程序可能会执行一些未定义的行为是不可判定的。 但是,可以确定一个特定的C ++ 执行是否产生未定义的行为。 做到这一点的一种方法是制作一个C ++解释器,按照spec中定义的定义遍历代码,在每个点确定代码是否有未定义的行为。 这不会检测到未在特定程序执行中发生的未定义行为,但会发现在程序中实际显示的任何未定义的行为。 这与TM确定TM是否接受一些input的图灵可识别性有关,即使它一般还是不可判定的。

谢谢!

这是一个很好的问题,但是让我想一想,为什么我认为这是不可能的(或者至less非常困难)。

据推测,这样的实现几乎是一个C ++ 解释器 ,或者至less是更像Lisp或Java的编译器。 这将需要为每个指针保留额外的数据,以确保您没有执行数组之外的算术或取消引用已经释放的东西或任何东西。

现在,请考虑以下代码:

 int *p = new int; delete p; int *q = new int; if (p == q) *p = 17; 

*p = 17未定义的行为? 一方面它解除了对它的解除。 另一方面,解引用q很好, p == q

但这不是重点。 关键在于if是否完全取决于堆实现的细节,这可能因实现而异。 所以用一些实际的未定义的行为来replace*p = 17 ,并且你有一个程序,可能在正常的编译器上爆炸,但在假想的“UB检测器”上运行良好。 (一个典型的C ++实现将使用一个LIFO空闲列表,所以这些指针有一个很好的机会是平等的。一个假设的“UB检测器”可能更像垃圾收集语言,以检测使用后的问题。

换句话说,我怀疑,仅仅由实现定义的行为的存在使得写一个适用于所有程序的“UB检测器”是不可能的。

也就是说,创build一个“超级严格的C ++编译器”的项目将会非常有趣。 让我知道如果你想开始一个。 🙂

使用g++

 -Wall -Werror -pedantic-error 

(最好还有一个适当的参数)将会捡起不lessUB的情况


事情 – -Wall让你包括:

-pedantic
发布严格的ISO C和ISO C ++所要求的所有警告; 拒绝所有使用禁止扩展的程序,以及其他一些不遵循ISO C和ISO C ++的程序。 对于ISO C,遵循由所使用的任何-std选项指定的ISO C标准版本。

-Winit-self仅限于 C,C ++,Objective-C和Objective-C ++)
警告使用自己初始化的未初始化variables。 请注意,此选项只能与-Wuninitialized选项一起使用,而该选项仅适用于-O1及更高版本。

-Wuninitialized
如果自动variables没有被初始化,或者variables可能被“setjmp”调用破坏,就会报警。

和各种不允许的事情,你可以做指定符printfscanf家庭function。

John Regehr在查找未定义的行为错误通过查找Dead Code指出一个名为STACK的工具,我从该网站引用( 重点是我的 ):

优化不稳定的代码(简称不稳定代码)是一类新兴的软件错误: 由于程序中未定义的行为,编译器优化意外地消除了代码。 许多系统中都存在不稳定的代码,包括Linux内核和Postgres数据库服务器。 不稳定代码的后果范围从错误的function到缺less安全检查。

STACK是一个静态检查器,用于检测C / C ++程序中不稳定的代码 。 将STACK应用于广泛使用的系统已经发现了160个已经被开发者确认和修复的新bug

另外在C ++ 11中,对于constexprvariables和函数的情况, 应该在编译时捕获 未定义的行为

我们也有gcc ubsan :

GCC最近(版本4.9)获得了未定义的行为Sanitizer(ubsan),一个用于C和C ++语言的运行时检查器。 为了用ubsan检查你的程序,用-fsanitize = undefined选项编译和链接程序。 这样的仪表二进制文件必须执行; 如果ubsan检测到任何问题,则会输出“运行时错误:”消息,并且在大多数情况下继续执行程序。

和铿锵静态分析器 ,其中包括许多检查未定义的行为。 例如clangs -sanitize包括-fsanitize=undefined检查:

-fsanitize = undefined:快速且兼容的未定义行为检查器。 启用未定义的行为检查,这些检查的运行时间成本较小,不会影响地址空间布局或ABI。 这包括下面列出的除unsigned-integer-overflow之外的所有检查。

对于C,我们可以看看他的文章是时候认真对待利用未定义的行为了 :

[…]我承认没有亲自通过最好的dynamic未定义的行为检查器 GCC或LLVM gock必要的消费:KCC和Frama-C […]

这是一个链接到KCC和我引用:

[…]如果你试图运行一个未定义的程序(或者我们缺less语义的程序),程序将会卡住。 该消息应该告诉你它卡在哪里,并可能给出一个暗示为什么。 如果你想帮助解密输出,或帮助理解为什么该程序是未定义的,请将您的.kdump文件发送给我们。[…]

这里有一个Frama-C的链接 ,这是一篇文章 ,第一次使用Frama-C作为C解释器,并且是该文章的附录 。

Cla有一套卫生消毒剂 ,可以捕捉各种forms的不确定行为。 他们的最终目标是能够捕捉所有C ++核心语言未定义的行为,但是检查一些棘手的未定义行为现在已经不存在了。

对于一个体面的卫生消毒剂,请尝试:

 clang++ -fsanitize=undefined,address 

-fsanitize=address检查使用坏的指针(不指向有效的内存),- -fsanitize=undefined启用一组轻量级的UB检查(整数溢出,坏的移位,未alignment的指针,…)。

-fsanitize=memory (用于检测未初始化的内存读取)和-fsanitize=thread (用于检测数据-fsanitize=thread )也是有用的,但这两者都不能与-fsanitize=address结合使用,因为这三者都会对程序的地址空间。

您可能想阅读有关SAFECode 。

这是来自伊利诺伊大学的一个研究项目,目标是在头版上说明(上面链接):

SAFECode项目的目的是在没有垃圾回收的情况下启用程序安全性,并在尽可能less的运行时检查时使用静态分析,并在必要时进行运行时检查。 SAFECode使用本项目开发的积极的编译器技术,定义了一个代码表示,其语义限制最小,旨在实现静态安全执行。

我真正感兴趣的是每当程序可以被certificate是静态正确的,就消除运行时检查,例如:

 int array[N]; for (i = 0; i != N; ++i) { array[i] = 0; } 

不应该比普通版本带来更多的开销。

就我记得, Clang对于未定义的行为有一些保证,但我不能把它放在手上。

clang编译器可以检测到一些未定义的行为,并警告他们。 可能不尽如人意,但这绝对是一个好的开始。

不幸的是我不知道有这样的工具。 通常UB被定义为这样,因为编译器在所有情况下都很难或不可能诊断它。

事实上,你最好的工具可能是编译器警告:他们经常警告UBtypes的项目(例如,基类中的非虚拟析构函数,滥用严格别名规则等)。

代码审查也可以帮助抓住UB依赖的情况。

那么你必须依靠valgrind来捕获其余的案件。

根据可计算性理论,作为一个侧面的观察,你不能有一个程序来检测所有可能的未定义的行为。

您只能使用启发式的工具,并检测某些遵循特定模式的特定情况。 或者你可以在某些情况下certificate一个程序的行为是你想要的。 但一般来说,你无法检测到未定义的行为。

编辑

如果一个程序没有终止(挂起,永远循环)给定的input,那么它的输出是不确定的。

如果你同意这个定义,那么确定一个程序是否终止是众所周知的“暂停问题”,这个问题已经被certificate是不可判定的,即没有程序(图灵机,C程序,C ++程序,Pascal程序)无论什么语言)一般可以解决这个问题。

简单地说,如果Q(I)终止,则不存在程序P可以input任何程序Q和input数据I并作为输出TRUE输出,否则如果Q(I)没有终止则打印FALSE。

欲了解更多信息,你可以看看http://en.wikipedia.org/wiki/Halting_problem

未定义的行为是未定义的 。 正如其他人所build议的那样,你所能做的最好的是顺从标准,但是你不能testing什么是未定义的,因为你不知道它是什么。 如果你知道它是什么,标准指定它,它不会是未定义的。

但是,如果你出于某种原因,实际上依赖于标准所说的未定义的东西 ,并且得到一个特定的结果,那么你可以select定义它,并且写一些unit testing来确认对于你的特定的构build,它是定义。 然而,只要可能,简单地避免未定义的行为就好多了。

看一看PCLint在C ++中检测很多不好的东西的时候是非常不错的。

这是它捕获的一个子集