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

时间:2011-08-30 02:01:22

标签: c++ undefined-behavior

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

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

此工具是否存在?或者说让它四处躺着会有用吗?

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

谢谢!

10 个答案:

答案 0 :(得分:20)

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

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

现在,请考虑以下代码:

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

if (p == q)
    *p = 17;

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

但这不是重点。关键是if是否评估为真取决于堆实现的细节,这可能因实现而异。因此,用一些实际的未定义行为替换*p = 17,并且你有一个程序可能会在普通的编译器上爆炸,但在你假设的“UB探测器”上运行正常。 (典型的C ++实现将使用LIFO空闲列表,因此指针很有可能是平等的。假设的“UB检测器”可能更像垃圾收集语言,以便检测释放后使用问题。)< / p>

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

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

答案 1 :(得分:15)

John Regehr中的{p> Finding Undefined Behavior Bugs by Finding Dead Code指出了一个名为STACK的工具,我引用该网站(强调我的):

  

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

     

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

C ++ 11 constexpr 变量和函数的情况未定义的行为 should be caught at compile time

我们还有gcc ubsan

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

Clang Static Analyzer包含many checks未定义的行为。例如,clangs -fsanitize检查包含-fsanitize=undefined

  

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

对于 C ,我们可以查看他的文章It’s Time to Get Serious About Exploiting Undefined Behavior,其中说:

  

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

这是一个link to kcc,我引用:

  

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

这里有一个link to Frama-C,一个article,其中首次使用Frama-C作为C解释器,并对文章addendum进行了描述。

答案 2 :(得分:11)

使用g++

-Wall -Werror -pedantic-error

(最好还带有适当的-std参数)将会收到很多U.B的案例。


-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家庭功能的说明符执行的各种不允许的操作。

答案 3 :(得分:10)

Clang有a suite of sanitizers可以捕获各种形式的未定义行为。他们最终的目标是能够捕获所有C ++核心语言未定义的行为,但现在缺少检查一些棘手的未定义行为形式。

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

clang++ -fsanitize=undefined,address

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

-fsanitize=memory(用于检测未初始化的内存读取)和-fsanitize=thread(用于检测数据竞争)也很有用,但这些都不能与-fsanitize=address组合,也不能与其他组合使用,因为所有这些都是三者对该计划的地址空间具有侵略性影响。

答案 4 :(得分:5)

您可能想了解SAFECode

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

  

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

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

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

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

据我所知,以较轻的方式Clang对未定义的行为有一些保证,但我无法抓住它......

答案 5 :(得分:3)

clang编译器可以检测到一些未定义的行为并发出警告。可能没有你想要的那么完整,但它绝对是一个好的开始。

答案 6 :(得分:3)

不幸的是我不知道任何这样的工具。通常,UB的定义正是因为在所有情况下编译器很难或不可能对其进行诊断。

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

代码审查还可以帮助捕获UB所依赖的案例。

然后你必须依靠valgrind来捕获剩余的案例。

答案 7 :(得分:1)

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

您只能使用启发式检测工具并检测某些遵循特定模式的特定情况。或者,您可以在某些情况下证明程序的行为符合您的要求。但是你通常无法检测到未定义的行为。

修改

如果程序没有在给定输入上终止(挂起,永远循环),那么它的输出是未定义的。

如果您对此定义达成一致,那么确定程序是否终止是众所周知的“暂停问题”,这已被证明是不可判定的,即不存在程序(图灵机,C程序,C ++程序,Pascal)程序,无论用哪种语言)都可以解决这个问题。

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

有关详细信息,请查看http://en.wikipedia.org/wiki/Halting_problem

答案 8 :(得分:0)

未定义的行为未定义。你可以做的最好的就是按照其他人所建议的那样迂腐地遵守标准,然而,你无法测试未定义的内容,因为你不知道它是什么。如果您知道它是什么,并且标准指定了它,那么它就不会被定义。

但是,如果由于某种原因,实际上依赖于标准所说的 undefined ,并且它会产生特定的结果,那么您可以选择定义它,并编写一些单元测试确认对于您的特定构建,它已定义。然而,尽可能简单地避免未定义的行为要好得多。

答案 9 :(得分:-1)

看看PCLint它在检测C ++中的很多坏事方面相当不错。

Here是其捕捉的一部分