定义未定义的行为

Defining Undefined Behavior

本文关键字:未定义 定义      更新时间:2023-10-16

是否存在任何c++(和/或C)的实现保证任何时候调用未定义的行为,它将发出错误信号?显然,这样的实现不可能像标准c++实现那样高效,但它可能是一个有用的调试/测试工具。

如果这样的实现不存在,那么是否有任何实际的原因使它不可能实现?或者仅仅是因为还没有人去做实现它的工作?

编辑: 更精确一点:我希望有一个编译器允许我断言,对于给定的运行的c++程序运行到完成,该运行的任何部分都不涉及未定义行为。

是,也不是。

我相当肯定,从实际目的来看,实现可以使c++成为一种安全的语言,这意味着每个操作都有良好定义的行为。当然,这会带来巨大的开销,并且可能在某些情况下根本不可行,例如多线程代码中的竞争条件。

现在,问题是这不能保证你的代码在其他实现中定义!也就是说,它仍然可以调用UB。例如,观察下面的代码:

int a;
int* b;
int foo() {
  a = 5;
  b = &a;
  return 0;
}
int bar() {
  *b = a;
  return 0;
}
int main() {
  std::cout << foo() << bar() << std::endl;
}

根据标准,foobar的调用顺序由实施决定。现在,在一个安全的实现中,必须定义这个顺序,很可能是从左到右的求值。问题是,从右到左的求值会调用UB,直到在不安全的实现上运行它才会被捕获。安全实现可以简单地编译每个求值顺序的排列,或者做一些静态分析,但这很快就变得不可行,而且可能无法确定。

总之,如果存在这样的实现,它会给你一种虚假的安全感。

新的C标准在新的附录L中有一个有趣的列表,标题很粗糙,叫做"Analyzability"。它讨论的是所谓的临界UB。其中包括:

  • 对象在生命周期(6.2.4)之外被引用。
  • 指针用于调用与被引用对象类型不兼容的函数
  • 程序试图修改字符串字面值

所有这些都是不可能或很难捕获的UB,因为它们通常不能在编译时完全测试。这是因为一个有效的C(或c++)程序是由几个编译单元组成的,这些编译单元可能彼此不太了解。例如,如果一个程序将指向字符串字面值的指针传递给具有char*参数的函数,或者更糟糕的是,一个程序从静态变量中丢弃了const属性。

为顺序C的大子集检测大量未定义行为的两个C解释器是KCC以及Frama-C的价值分析。它们都用于确保自动生成、自动缩减的随机C程序适合报告C编译器中的错误。

从KCC的网页:

这项工作的主要目的之一是检测未定义的能力程序(例如,读取无效内存的程序)。

C语言方言的第三个解释器是CompCert的解释器模式(writeup)。它检测所有在经过认证的C编译器CompCert的输入语言中未定义的行为。CompCert的输入语言本质上是C语言,但它呈现了一些标准中未定义的行为(例如,有符号算术溢出被定义为计算2的补码结果)。

事实上,在这个答案中提到的所有三个解释者都在实用主义的名义下做出了艰难的选择。

将某些东西定义为"未定义行为"的全部意义在于避免在编译器中检测这种情况。它是这样定义的,这样编译器就可以为各种各样的平台和体系结构而构建,这样硬件和软件就不必有特定的功能"只是为了检测未定义的行为"。想象一下,你有一个内存子系统,它不能检测到你是否在写真正的内存——编译器或运行时系统如何检测到你刚刚做了somepointer = rand(); *somepointer = 42;

你可以检测到一些情况。但是,如果要求检测所有这些疾病,将会使生活变得非常困难。

在原始问题中的编辑:我仍然不认为这是合理的实现在c中。有这么多的自由做几乎任何事情(指针几乎任何东西,这些指针可以转换,索引,重新计算,和所有其他方式的事情),并将能够导致所有方式的未定义行为。这里有一个C语言中所有未定义行为的列表-它列出了186种不同的未定义行为,从反斜杠作为文件的最后一个字符(可能导致编译器错误,但没有定义为一个)到"由bsearch或qsort函数调用的比较函数返回的排序值不一致"。

你究竟如何编写一个编译器来检查传递给bsearch或qsort的函数是否一致地排序值?当然,如果传入比较函数的数据是简单类型,比如整数,那么这并不困难,但是如果数据类型是复杂类型,比如

struct {
    char name[20];
    char street[20];
    int age;
    char post_code[10];
};

,程序员决定根据升序的姓名、升序的街道、降序的年龄和升序的邮政编码对数据进行排序?如果这是你想要的,但代码搞砸了,post代码比较返回不一致的结果,事情就会出错,但很难正式检查这种情况。还有许多其他类似的模糊和复杂的问题。当然,你的代码可能不会对名字和地址等进行排序,但有人可能会在某个时候写这样的东西。