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

tem*_*def 59 c++ undefined-behavior

C++中的大量操作会导致未定义的行为,其中规范完全静音程序的行为应该是什么,并允许任何事情发生.因此,有各种各样的情况,人们有代码在调试但不是发布模式编译,或者直到看似无关的更改,或者在一台机器而不是另一台机器上工作,等等.

我的问题是是否有一个实用程序查看C++代码的执行并标记程序调用未定义行为的所有实例.虽然我们有很好的工具,比如valgrind和检查过的STL实现,但这些并不像我想的那么强 - 例如,如果你丢弃了你仍然分配的内存,那么valgrind会有误报,并且检查了STL实现不会通过基类指针捕获删除.

这个工具存在吗?或者让它躺在身边甚至是有用的?

编辑:我知道一般来说静态检查C++程序是否可能执行具有未定义行为的东西是不可判定的.但是,可以确定C++ 的特定执行是否产生了未定义的行为.实现此目的的一种方法是创建一个C++解释器,根据规范中列出的定义逐步执行代码,在每个点确定代码是否具有未定义的行为.这不会检测特定程序执行中未发生的未定义行为,但会发现任何实际在程序中显示的未定义行为.这与图灵识别如何确定TM是否接受某些输入有关,即使它通常仍然是不可判定的.

谢谢!

Nem*_*emo 20

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

据推测,这样的实现几乎可以是C++ 解释器,或者至少是一个更像Lisp或Java的编译器.它需要为每个指针保留额外的数据,以确保您不在数组之外执行算术或取消引用已经释放的内容或其他内容.

现在,请考虑以下代码:

int *p = new int;
delete p;
int *q = new int;

if (p == q)
    *p = 17;
Run Code Online (Sandbox Code Playgroud)

*p = 17未定义的行为?一方面,它p在被释放后取消引用.另一方面,解除引用q是好的p == q...

但这不是重点.重点是,if评估结果是否完全取决于堆实现的细节,这可能因实现而异.因此,用*p = 17一些实际的未定义行为替换,你有一个程序,可能很好地炸毁普通的编译器,但在假设的"UB探测器"上运行正常.(典型的C++实现将使用LIFO空闲列表,因此指针很有可能是平等的.假设的"UB检测器"可能更像垃圾收集语言,以便检测免费使用后的问题.)

换句话说,仅仅实现定义的行为的存在使我无法编写适用于所有程序的"UB检测器",我怀疑.

也就是说,创建"超级严格的C++编译器"的项目非常有趣.如果你想开始一个,请告诉我.:-)

  • 实际上,这段代码是未定义的.释放后,您无法以任何方式使用指针(包括检查其地址). (7认同)
  • @MSalters:见[这个答案](http://stackoverflow.com/a/20730923/768469).看来`p == q`测试在C++ 03中是未定义的,在C++ 11中是实现定义的,这两个对我来说都是新闻. (3认同)
  • 代码完全合法.C++中的实际规则是`*p`必须引用一个对象.在`delete`之后不是这种情况,而是在`if(p == q)`之后的情况.哦,这里的实际行为是_unspecified_,而不是_implementation-defined_,但这确实是一个小的差异. (2认同)
  • @MSalters不,不是。p已被删除,因此即使将其与另一个指针进行比较也会导致未定义的行为。 (2认同)
  • 我可以设想一个可以检测这种行为的实现。如果指针包含地址、范围、*和代号*,则相等运算符可以仅检查地址,但间接可以检查所有三个。 (2认同)

Sha*_*our 16

约翰·雷吉汉弗通过查找死代码查找未定义行为错误指出一个工具,称为STACK,我从网站(行情重点煤矿):

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

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

同样在C++ 11中,对于constexpr变量和函数的情况,应该在编译时捕获未定义的行为 .

我们也有gcc ubsan:

GCC最近(版本4.9)获得了Undefined Behavior Sanitizer(ubsan),它是C和C++语言的运行时检查程序.要使用ubsan检查程序,请使用-fsanitize = undefined选项编译和链接程序.必须执行这样的检测二进制文件; 如果ubsan检测到任何问题,它会输出"运行时错误:"消息,并且在大多数情况下会继续执行该程序.

Clang静态分析器,包括许多未定义行为的检查.例如clangs -fsanitize检查包括-fsanitize=undefined:

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

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

[..]我承认,通过最佳可用的动态未定义行为检查器:KCC和Frama-C,我个人没有必要加入GCC或LLVM .[...]

这是kcc链接,我引用:

[...]如果您尝试运行未定义的程序(或者我们缺少语义的程序),程序将会卡住.该消息应告诉您卡在哪里,并可能提示原因.如果您需要帮助解密输出,或者帮助理解程序未定义的原因,请将.kdump文件发送给我们.[...]

这里是Frama-C链接,这篇文章描述了Frama-C作为C语言翻译的第一次使用,以及该文章的附录.


dmc*_*kee 11

运用 g++

-Wall -Werror -pedantic-error
Run Code Online (Sandbox Code Playgroud)

(最好还有一个适当的-std论据)将会收集相当多的UB案例


-Wall让你的事情包括:

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

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

-Wuninitialized
如果在没有首先初始化的情况下使用自动变量或者如果变量可能被"setjmp"调用破坏,则发出警告.

和你可以用说明符printfscanf家庭功能做的各种不允许的事情.

  • 确实如此,尽管它不会插入对可能最终导致未定义行为的运行时操作的检查。 (2认同)
  • 绝对这只会检测所有 UB 的一个子集,但是您*确实*会得到我上面没有列出的别名检查,这也应该让您了解一些动态故障的情况。 (2认同)

Ric*_*ith 10

Clang有一套清洁剂,可以捕捉各种形式的未定义行为.他们最终的目标是能够捕获所有C++核心语言未定义的行为,但是现在缺少对一些棘手形式的未定义行为的检查.

对于一套体面的消毒剂,请尝试:

clang++ -fsanitize=undefined,address
Run Code Online (Sandbox Code Playgroud)

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

-fsanitize=memory(用于检测未初始化的存储器读取)和-fsanitize=thread(用于检测数据竞争)也是有用的,但这些都不能彼此组合-fsanitize=address或相互组合,因为这三者都对程序的地址空间具有侵入性影响.


Mat*_* M. 5

您可能想要阅读有关SAFECode的信息.

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

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

对我来说真正有趣的是,只要程序可以被证明是静态正确的,就可以取消运行时检查,例如:

int array[N];
for (i = 0; i != N; ++i) { array[i] = 0; }
Run Code Online (Sandbox Code Playgroud)

不应该比常规版本产生更多的开销.

在一个较轻的时候,就我记忆中而言,Clang对于未定义的行为有一些保证,但是我无法抓住它......