未指定,未定义和实现定义的行为WIKI for C

Lor*_*ica 10 c undefined-behavior unspecified-behavior

虽然在SO上有很多关于这个主题的链接,但我认为有些东西缺失:用简单的语言明确解释未指定行为(UsB),未定义行为(UB)和实现定义行为(IDB )之间的区别)详细但容易解释任何用例和示例.

注意:为了这个WIKI的紧凑性,我提出了UsB的首字母缩略词,但是不要指望在其他地方使用它.

我知道这可能看起来与其他帖子(它更接近的是这个)的重复,但在任何人将此标记为重复之前,请考虑我已经找到的所有材料的问题(我将要制作这个帖子中的社区WIKI):

  • 太多分散的例子.当然,例子并不坏,但有时人们无法找到一个很好地适应他手头问题的例子,因此它们可能会令人困惑(特别是对于新手).

  • 示例通常只是代码,但解释很少.在这些微妙的问题上,特别是(相对)新手,更自上而下的方法可能更好:首先是一个清晰,简单的解释与抽象(但不是法律)的描述,然后是 一些简单的例子,解释为什么他们触发一些行为.

  • 有些帖子经常混合使用C和C++示例.C和C++有时与他们认为的UsB,UB和IDB不一致,所以一个例子可能会误导那些不熟悉这两种语言的人.

  • 当给出UsB,UB和IDB的定义时,通常它是标准的简单引用,有时可能不清楚或难以为新手消化.

  • 有时引用标准是不完整的.许多帖子只引用了对手头问题有用的部分标准,这很好,但缺乏一般性.此外,对标准的引用通常没有任何解释(对初学者不利).

由于我自己不是这个主题的超级专家,我将建立一个社区WIKI,以便任何有兴趣的人都可以贡献并改进答案.

为了不破坏我创建一个结构良好的初学友好的WIKI的目的,我希望海报在编辑WIKI时遵循一些简单的指导原则:

  • 对您的用例进行分类.尝试将您的示例/代码放在已存在的类别下(如果适用),否则创建一个新类别.

  • 首先是简单的单词描述.首先用简单的词语描述(当然,不要过于简单化 - 质量第一!)你想要做的例子或要点.然后放入代码示例或引用.

  • 引用标准参考.不要发布各种标准的片段,但要提供明确的参考资料(例如C99 WG14/N ......第1.4.7节,第......段),在可能的情况下发布相关资源的链接.

  • 喜欢免费的在线资源.如果你想引用书籍或非免费可用的资源(可能会提高WIKI的质量),但尝试添加一些免费资源的链接.这对ISO标准尤为重要.欢迎您添加官方标准的链接,但也尝试添加等效链接以免费提供草稿.请不要将参考链接替换为参考官方标准,添加到它们.甚至某些大学的某些计算机科学系也没有ISO标准的副本,更不用说大多数程序员了!

  • 除非确实需要,否则不要发布代码.仅在仅使用普通英语的解释尴尬或不清楚时才发布代码.尝试将代码示例限制为单行.发布指向其他SO Q&A的链接.

  • 不要发布C++示例.我希望这能成为C的一种常见问题解答(如果有人想为C++启动一个双线程,那将是很棒的).与C++的相关差异是受欢迎的,但仅作为附注.这是在你彻底解释C案例之后,你可以添加一些关于C++的陈述,如果这对C程序员在切换到C++时会有所帮助,但我不希望看到的例子超过20%的C++.通常一个简单的注释,如"(C++在这种情况下表现不同)"加上相关链接就足够了.

因为我对SO很新,所以我希望通过这种方式开始问答我不会破坏任何规则.对不起,如果是这样的话.欢迎mods让我知道它.

Lor*_*ica 11

C标准定义UsB,UB和IDB的方式可归纳如下:

未指定的行为(UsB)

这是一种行为,标准提供了一些替代方案,其中实施必须选择,但它并没有强制要求如何以及何时做出选择.换句话说,实现必须接受用户代码触发该行为而不会出错,并且必须符合标准给出的替代方案之一.

请注意,不需要实现来记录有关所做选择的任何内容.这些选择也可能是非确定性的或依赖于(以未记录的方式)编译器选项.

总结一下:标准提供了一些可供选择的可能性,实现选择何时以及如何选择和应用特定替代方案.

请注意,该标准可能提供了大量的替代方案.典型示例是未显式初始化的局部变量的初始值.该标准表示只要它是变量数据类型的有效值,就不指定该值.

更具体地考虑一个int变量:一个实现可以自由选择任何int值,这个选择可以是完全随机的,非确定性的,也可以是实现的奇思妙想,不需要记录它的任何内容.只要实施保持在标准规定的限制范围内,这是可以的,用户不能抱怨.

未定义的行为(UB)

由于命名表明这是C标准没有强加或保证程序应该或应该做什么的情况.所有赌注都已关闭.这样的情况:

  • 使程序错误不可移植

  • 实现中不需要任何内容

这是一个非常讨厌的情况:只要有一段代码具有未定义的行为,整个程序就被认为是错误的,并且标准允许执行所有操作.

换句话说,只要涉及触发UB的程序,UB的原因的存在允许实现完全忽略标准.

请注意,在这种情况下的实际行为可能涵盖无限范围的可能性,以下内容绝不是详尽的列表:

  • 可能会发出编译时错误.
  • 可能会发出运行时错误.
  • 问题完全被忽略(这可能会导致程序错误).
  • 编译器默默地将UB代码作为优化抛出.
  • 您的硬盘可能已格式化.
  • 您的计算机可能会清除您的银行帐户并要求您的女朋友约会.

我希望最后两件(真的)可以让你对UB的肮脏感有正确的感觉.即使大多数实现都不会插入必要的代码来格式化硬盘驱动器,但真正的编译器会进行优化!

术语注意:有时人们认为标准认为其实现/系统/环境中的UB源代码的某些代码以文档的方式工作,因此它不能真正是UB.这种推理是错误的,但这是一个常见的(并且有些可理解的)误解:当术语UB(以及UsB和IDB)用于 C语境时,它意味着一个技术术语,其精确含义由标准定义( S).特别是"未定义"这个词失去了它的日常意义.因此,显示错误或不可移植程序产生"明确定义"行为作为反例的示例是没有意义的.如果你尝试,你真的很想念.UB意味着您失去了标准的所有保证.如果您的实现提供了扩展,那么您的保证只是您的实现.如果你使用那个扩展你的程序不再是一个符合C的程序(从某种意义上说,它不再是一个C程序,因为它不再遵循标准了!).

未定义行为的有用性

关于UB的一个常见问题就是这些问题:"如果UB是如此令人讨厌,为什么标准要求实施在面对UB时发出错误?"

首先,优化.允许实现不检查UB的可能原因允许进行大量优化,使C程序非常高效.这是C的功能之一,虽然它使C成为初学者的许多陷阱的来源.

其次,标准中UB的存在允许符合要求的实现提供对C的扩展而不被视为整体不符合.

只要实现的行为符合一致性程序的要求,它本身就符合要求,尽管它可能提供可能在特定平台上有用的非标准设施.当然,使用这些设施的程序将是不可移植的,并且将依赖于记录的UB,即根据标准的UB行为,但是实现文档作为扩展.

实现定义的行为(IDB)

这是一种可以类似于UsB的方式描述的行为:标准提供了一些替代方案,实现选择了一个,但实现需要准确记录如何做出选择.

这意味着必须为阅读其编译器文档的用户提供足够的信息,以准确预测特定情况下将发生的情况.

请注意,不能完全记录IDB的实现不能视为符合要求.符合标准的实现必须准确记录标准声明IDB的任何情况下发生的情况.



未指定行为的示例

评估顺序

函数参数

函数参数的评估顺序未指定EXP30-C.

例如,c(a(), b());未指定是在a之前还是之后调用该函数b.唯一的保证是在c函数之前调用它们.



未定义行为的示例

指针

取消引用空指针

空指针用于表示指针未指向有效内存.因此,尝试通过空指针读取或写入内存没有多大意义.

从技术上讲,这是未定义的行为.但是,由于这是一个非常常见的错误来源,因此大多数C环境确保大多数取消引用空指针的尝试都会立即使程序崩溃(通常会因为分段错误而将其终止).由于引用数组和/或结构时涉及指针算法,因此这种保护并不完美,因此即使使用现代工具,取消引用空指针也可能会格式化您的硬盘.

取消引用未初始化的指针

就像空指针一样,在明确设置其值之前取消引用指针是UB.与空指针不同,大多数环境不提供任何安全网来抵御这种错误,除了编译器可以警告它.无论如何,如果你编译你的代码,你很可能会遇到UB的整个肮脏.

取消引用无效指针

无效指针是一个指针,其中包含的地址不在任何已分配的内存区域内.创建无效指针的常用方法是调用free()(在调用之后,指针将无效,这几乎是调用点free()),或者使用指针算法来获取超出已分配内存块限制的地址.

这是指针解除引用UB的最邪恶的变种:没有安全网,没有编译器警告,只有代码可以做任何事情.通常,它确实如此:大多数恶意软件攻击在程序中使用这种UB行为,使程序按照他们希望的行为(如安装木马,键盘记录程序,加密硬盘等).使用这种UB,格式化硬盘的可能性变得非常真实!

抛弃常数

如果我们将对象声明为const,我们向编译器发出一个承诺,即我们永远不会更改该对象的值.在许多情况下,编译器会发现这种无效的修改并向我们大喊大叫.但是,如果我们像在这个片段中那样抛弃constness:

int const a = 42;
...
int* ap0 = &a;      //< error, compiler will tell us
int* ap1 = (int*)a; //< silences the compiler
...
*ap1 = 43;          //< UB ==> program crash?
Run Code Online (Sandbox Code Playgroud)

编译器可能无法跟踪此无效访问,将代码编译为可执行文件,并且仅在运行时检测到无效访问并导致程序崩溃.

第2类

把标题放在这里!

把你的解释放在这里!



实现定义的行为的示例

第1类

把标题放在这里!

把你的解释放在这里!