尽管SO上有很多关于这个主题的链接,但我认为有些内容缺失:用明确的语言明确解释未指明的行为(UsB)之间的差异,未定义的行为(UB)和实现定义的行为(IDB),详细但易于解释任何用例和示例。
注意:我为了这个WIKI的紧凑性而提出了 UsB 的首字母缩略词,但是不要指望在其他地方使用它。
我知道这可能看起来与其他帖子(更接近的帖子是this)重复,但 之前有人将此标记为重复 ,请考虑我已经找到的所有材料有什么问题(我将在这篇文章中建立一个社区WIKI):
太多分散的例子。当然,例子并不坏,但有时人们无法找到一个很好地适应他手头问题的例子,因此它们可能会令人困惑(特别是对于新手)。
示例通常只是代码,但解释很少。在这些微妙的问题上,特别是(相对)新手,更自上而下的方法可能更好:首先是一个清晰,简单的解释,抽象(但不是法律)的描述,然后一些简单的例子< em>解释为什么他们触发某些行为。
有些帖子经常混合使用C和C ++示例。 C和C ++有时与他们认为的UsB,UB和IDB不一致,所以一个例子可能会误导那些不熟悉这两种语言的人。
当给出UsB,UB和IDB的定义时,通常它是标准的简单引用,有时可能不清楚或难以为新手消化。
有时引用标准是不完整的。许多帖子只引用了对手头问题有用的部分标准,这很好,但缺乏一般性。此外,对标准的引用通常没有任何解释(对初学者不利)。
由于我自己并不是这个主题的超级专家,我将建立一个社区WIKI,以便任何有兴趣的人都可以贡献并改进答案。
为了不破坏我创建结构化初学者友好型WIKI的目的,我希望海报在编辑WIKI时遵循一些简单的指导原则:
对您的用例进行分类。尝试将您的示例/代码置于现有类别(如果适用),否则请创建一个新类别。
首先是简单的单词描述。首先用简单的单词描述(当然,不要过度简化 - 质量第一!)你要做的例子或要点。 然后放置代码示例或引用。
引用标准参考。不要发布各种标准的片段,但要提供明确的参考资料(例如C99 WG14 / N ......第1.4.7节,段落......如果可能的话,和发布相关资源的链接。
喜欢免费的在线资源。如果您想引用书籍或非免费资源,这些资源可以(并且可以提高WIKI的质量),但也尝试添加一些链接释放资源。这对ISO标准尤为重要。欢迎您添加官方标准的链接,但也尝试添加等效链接以免费提供草稿。请不要替换草稿链接,并提及官方标准,添加。甚至某些大学的某些计算机科学系也没有ISO标准的副本,更不用说大多数程序员了!
除非确实有必要,否则不要发布代码。只有在仅使用普通英语的说明笨拙或不清楚时才发布代码。尝试将代码示例限制为单行。发布指向其他SO Q&amp; A的链接。
不要发布C ++示例。我希望这会成为一种常见问题解答 for C (如果有人我想为C ++启动一个双线程,但这很好。与C ++的相关差异是受欢迎的,但仅作为附注。这是在你彻底解释C案例后,你可以添加一些关于C ++的陈述,如果这对C程序员在切换到C ++时会有所帮助,但我不希望看到的例子超过20%的C ++。通常一个简单的注释,如“(在这种情况下C ++行为不同)”加上相关链接就足够了。
因为我对SO很新,所以我希望通过这种方式启动Q&amp; A,我不会违反任何规则。对不起,如果是这样的话。欢迎各位模特让我知道。
答案 0 :(得分:11)
C标准以可归纳如下的方式定义UsB,UB和IDB:
这是一种行为,标准提供了一些替代方案,其中实施必须选择,但它并不强制如何和时< / strong>要做出选择。换句话说,实现必须接受用户代码触发该行为而不会出错,并且必须符合标准给出的替代方案之一。
请注意,的实施不需要记录有关所做选择的任何内容。这些选择也可能是非确定性的或依赖于(以未记录的方式)编译器选项。
总结:标准提供了一些可供选择的可能性,实施选择何时以及如何选择和应用特定的替代方案。
请注意,该标准可能提供了大量的替代品。典型示例是未显式初始化的局部变量的初始值。该标准表示该值是未指定的,只要它是变量数据类型的有效值。
更具体地考虑一个int
变量:一个实现可以自由选择任何int
值,并且这个选择可以是完全随机的,非确定性的,或者是由于实现,,不需要记录任何关于它的。只要实施保持在标准规定的限制范围内,这是可以的,用户不能抱怨。
由于命名表明这是C标准没有强加或保证程序应该或应该做什么的情况。所有赌注都已关闭。这样的情况:
将程序呈现为错误或不可移植
完全不需要任何来自实施的内容
这是一个非常糟糕的情况:只要有一段代码具有未定义的行为,整个程序被认为是错误的并且标准允许实现一切强>
换句话说,只要涉及触发UB的程序,UB原因的存在就允许实现完全忽略标准。
请注意,在这种情况下的实际行为可能涵盖无限范围的可能性,以下内容绝不是详尽无遗的列表:
我希望最后两个(一半 - 严重)项目可以让你对UB的肮脏感有正确的感觉。尽管大多数实现都不会插入必要的代码来格式化硬盘,但真正的编译器会进行优化!
术语注意: 有时人们认为标准认为在其实现/系统/环境中使用UB 源代码的某些代码以文档化的方式工作, 因此 它不可能是真正的UB。 这种推理是错误的,但这是一个常见的(有些可理解的)误解:当在C上下文中使用术语UB(以及UsB和IDB) 时,它是意为技术术语,其精确含义由标准定义。尤其是&#34; undefined&#34;失去了日常的意义。因此,显示错误或不可移植程序产生的例子并没有明确定义&#34;定义明确的&#34;行为作为反例。如果你尝试,你真的很想念。 UB意味着您失去了标准的所有保证。如果您的实现提供了扩展,那么您的保证只是您的实现。如果你使用那个扩展程序,你的程序就不再是一个符合标准的C程序(从某种意义上说,它不再是一个C程序,因为它不再遵循标准!)。
关于UB的一个常见问题就是这些问题:&#34;如果UB是如此讨厌,为什么在面对UB时标准要求实现发出错误?&#34;
首先,优化。允许实现不检查UB的可能原因允许进行大量优化,使C程序非常高效。这是C的特征之一,虽然它使C成为初学者的许多陷阱的来源。
其次,标准中UB的存在允许符合标准的实现为C提供扩展,而不会被视为整体不符合。
只要实现的行为符合一致性程序的要求,它本身就符合要求,尽管它可能提供可能在特定平台上有用的非标准设施。当然,使用这些设施的程序将是 非便携式 ,并且将依赖于 记录的UB ,即UB的行为根据标准,但实施文件作为扩展。
这种行为可以用类似于UsB的方式描述:标准提供了一些替代方案,实现选择了一个,但是需要实现来准确记录选择的方式。
这意味着,必须为阅读其编译器文档的用户提供足够的信息,以便在具体情况下准确预测会发生什么。
请注意,不能完全记录IDB的实现不能视为符合要求。符合标准的实现必须准确记录标准声明IDB的任何情况。
函数参数的评估顺序未指定EXP30-C。
例如,在c(a(), b());
中,未指定在a
之前或之后是否调用函数b
。唯一的保证是在c
函数之前调用它们。
空指针用于表示指针未指向有效内存。因此,尝试通过空指针读取或写入内存没有多大意义。
从技术上讲,这是未定义的行为。但是,由于这是一个非常常见的错误来源,因此大多数C环境确保大多数取消引用空指针的尝试都会立即使程序崩溃(通常会因为分段错误而将其终止)。由于引用数组和/或结构时涉及指针算法,因此这种保护并不完美,因此即使使用现代工具,取消引用空指针也可能会格式化您的硬盘。
就像空指针一样,在明确设置其值之前取消引用指针是UB。与空指针不同,大多数环境不提供任何安全网来抵御这种错误,除了编译器可以警告它。无论如何,如果你编译代码,你很可能会遇到UB的整个肮脏。
无效指针是一个指针,其中包含的地址不在任何已分配的内存区域内。创建无效指针的常用方法是调用free()
(在调用之后,指针将无效,这几乎是调用free()
的点),或者使用指针算法来获取一个地址超出了已分配内存块的限制。
这是指针解除引用UB的最邪恶的变种:没有安全网,没有编译器警告,只有代码可以做任何事情。通常,它确实如此:大多数恶意软件攻击在程序中使用这种UB行为,使程序按照他们希望的行为(如安装木马,键盘记录程序,加密硬盘等)。使用这种UB,格式化硬盘驱动器的可能性变得非常真实!
如果我们将对象声明为const
,我们会向编译器发出一个承诺,即我们永远不会更改该对象的值。在许多情况下,编译器会发现这种无效的修改并向我们大喊大叫。但是如果我们像在这个片段中那样抛弃常量:
int const a = 42;
...
int* ap0 = &a; //< error, compiler will tell us
int* ap1 = (int*)a; //< silences the compiler
...
*ap1 = 43; //< UB ==> program crash?
编译器可能无法跟踪此无效访问,将代码编译为可执行文件,并且仅在运行时检测到无效访问并导致程序崩溃。
把你的解释放在这里!
把你的解释放在这里!
答案 1 :(得分:1)