未定义,未指定和实现定义的行为

C和C ++中未定义的,未指定的和实现定义的行为有什么区别?

未定义的行为是C和C ++语言的其中一个方面,对于来自其他语言的程序员可能会感到惊讶(其他语言试图更好地隐藏它)。 基本上,可以编写不可预测的C ++程序,尽pipe许多C ++编译器不会在程序中报告任何错误!

我们来看一个典型的例子:

#include <iostream> int main() { char* p = "hello!\n"; // yes I know, deprecated conversion p[0] = 'y'; p[5] = 'w'; std::cout << p; } 

variablesp指向string文字"hello!\n" ,下面的两个分配尝试修改string文字。 这个程序做什么? 根据C ++标准的第2.14.5节第11段,它调用未定义的行为

尝试修改string文字的效果是未定义的。

我可以听到人们尖叫“但是,等等,我可以编译这个没有问题,并获得输出yellow ”或“你是什么意思未定义,string文字存储在只读内存,所以第一次分配尝试导致核心转储” 。 这正是未定义行为的问题。 基本上,一旦你调用未定义的行为(甚至鼻恶魔),标准允许任何事情发生。 如果根据你的语言思维模式有一个“正确”的行为,那么这个模型就是错误的; C ++标准有唯一的投票时间。

未定义的行为的其他例子包括访问数组超出其界限, 解除引用空指针 , 在其生命周期结束后访问对象或编写巧妙的expression式,如i++ + ++i

C ++标准的1.9节还提到未定义行为的两个不太危险的兄弟, 未指定的行为实现定义的行为

本标准中的语义描述定义了一个参数化的非确定性抽象机器。

抽象机器的某些方面和操作在本标准中被描述为实现定义的 (例如, sizeof(int) )。 这些构成抽象机器的参数。 每个实施应包括描述其在这些方面的特点和行为的文件。

在本标准中,抽象机器的某些其他方面和操作未作规定 (例如,函数参数的评估顺序)。 在可能的情况下,本标准定义了一系列允许的行为。 这些定义了抽象机器的非确定性方面。

本标准中某些其他操作未定义 (例如,取消引用空指针的效果)。 [ 本标准对含有未定义行为的程序行为没有要求。结束注意 ]

具体来说,第1.3.24节规定:

允许的未定义的行为范围从完全忽略情况,以不可预知的结果 ,在翻译或程序执行期间以文档化的方式performance环境特征(不论是否发布诊断消息),终止翻译或执行(发行的诊断消息)。

你能做些什么来避免遇到未定义的行为? 基本上,你必须阅读那些知道他们在说什么的作者的好的C ++书籍 。 螺丝互联网教程。 螺丝bullschildt。

那么,这基本上是从标准直接复制粘贴

3.4.1 1个实现定义的行为未指定的行为,其中每个实现logging如何做出select

2示例实现定义的行为的一个示例是当有符号整数右移时高位传播。

3.4.3一个未定义的行为行为,如果使用不可移植的或错误的程序结构或错误的数据,本国际标准对此没有要求

2注意可能存在未定义的行为范围,包括忽略完全不可预知的结果,以翻译或程序执行的方式performance环境特征(无论是否发布诊断消息),终止翻译或执行发出诊断消息)。

3示例未定义行为的示例是整数溢出的行为。

3.4.4 1 未指明的行为使用未指明的价值,或本国际标准提供两种或两种以上可能性的情况下的其他行为,并且在任何情况下都没有提出进一步的要求

2示例未指定行为的示例是对函数的参数进行评估的顺序。

也许简单的措辞比标准的严格定义更容易理解。

实现定义的行为
该语言说,我们有数据types。 编译器供应商指定他们使用的尺寸,并提供他们所做的工作的文档。

未定义的行为
你做错了什么 例如,你在int中有一个非常大的值,不适合char 。 你如何把这个价值char ? 其实没有办法! 任何事情都可能发生,但最明智的是将int的第一个字节放在char 。 这样做只是分配第一个字节是错误的,但那就是发生了什么。

未指定的行为
这两个函数哪个先执行?

 void fun(int n, int m); int fun1() { cout << "fun1"; return 1; } int fun2() { cout << "fun2"; return 2; } ... fun(fun1(), fun2()); // which one is executed first? 

语言不指定评估,从左到右或从右到左! 所以一个不明确的行为可能会或可能不会导致一个不确定的行为,但是当然你的程序不应该产生一个不确定的行为。


@eSKay我认为你的问题值得编辑的答案澄清更多:)

fun(fun1(), fun2()); 是不是行为“实现定义”? 毕竟编译器必须select一个或另一个课程?

实现定义的和未指定的区别在于,编译器应该在第一种情况下select行为,但不必在第二种情况下。 例如,一个实现必须有且只有一个sizeof(int)定义。 所以,不能说sizeof(int)对于程序的某个部分是4,对于其他部分是8。 与未指定的行为不同,在编译器可以说OK的地方,我将从左到右评估这些参数,并且从右到左评估下一个函数的参数。 它可以发生在同一个程序中,这就是为什么它被称为未指定 。 事实上,如果指定了一些未指定的行为,C ++可以变得更容易。 看看Stroustrup博士的回答吧 :

据称,给编译器提供这种自由的东西与“普通的从左到右的评估”之间的区别是非常重要的。 我不相信,但是有无数的编译器利用自由和一些激情捍卫这种自由的人,变化将是困难的,可能需要几十年时间才能渗透到C和C ++世界的遥远angular落。 我很失望,并不是所有的编译器都会对诸如++ i + i ++之类的代码发出警告。 类似的,参数的评估顺序是不确定的。

国际海事组织(IMO)有太多的“东西”是未定义的,没有具体说明的,实现定义的等等。然而,这很容易说,甚至举出例子,但很难解决。 还应该指出的是,避免大部分问题并生成便携式代码并不是那么困难。

从官方的C原理文件

术语未指定的行为, 未定义的行为和实现定义的行为被用来对编写标准没有或者不能完全描述的属性的程序的结果进行分类。 采用这种分类的目标是允许实现中的某种变化,这允许执行质量成为市场中的主动力量,并允许某些stream行的扩展,而不去除标准的一致性。 标准附录F列出了属于这三类之一的行为。

未指定的行为给翻译程序提供了一定的自由度。 这个纬度没有延伸到没有翻译节目的地步。

未定义的行为使得执行者许可不会发现某些难以诊断的程序错误。 它还确定了可能符合语言扩展的领域:实现者可以通过提供官方未定义行为的定义来扩充语言。

实现定义的行为使实现者可以自由select合适的方法,但要求向用户解释这一select。 被指定为实现定义的行为通常是用户可以根据实现定义做出有意义的编码决定的行为。 实施者在决定实施定义应该是多么广泛时应该牢记这一标准。 与未指定的行为一样,只是没有翻译含有实现定义的行为的源代码并不是一个足够的回应。

未定义的行为与未指定的行为有一个简短的描述。

他们的最后总结:

总之,未指定的行为通常是您不应该担心的事情,除非您的软件必须是可移植的。 相反,未定义的行为总是不可取的,绝不应该发生。

历史上,“实现定义行为”和“未定义行为”均代表标准作者期望编写质量实现的人员将使用判断来决定哪些行为保证(如果有的话)对于运行在预期的目标。 高端数字处理代码的需求与低层系统代码的需求是大不相同的,UB和IDB都给编译器编写者提供了灵活性来满足这些不同的需求。 这两个类别都不要求实现的行为方式对于任何特定的目的是有用的,甚至不pipe用于任何目的。 声称适用于特定目的的质量实现, 无论标准是否要求,其行为都应符合此类目的。

实现定义行为和未定义行为之间唯一的区别在于,前者要求实现定义并logging一致的行为, 即使在实现可能没有任何用处的情况下也是如此 。 它们之间的分界线不在于定义行为的实现通常是否有用(编译器编写者在实际中是否应该定义有用的行为,而是标准是否要求),但是是否可能存在定义行为的实现同时代价高昂并没用 。 这样的实现可能存在的判断不以任何方式,forms或forms,暗示对在其他平台上支持定义的行为的有用性的任何判断。

不幸的是,自20世纪90年代中期以来,编译器作者已经开始将缺乏行为要求解释为行为保证不值得花钱的判断,即使是在他们至关重要的应用领域,甚至是几乎没有任何代价的系统上。 编译器作者并没有把UB看作是行使合理判断的邀请,而是开始把它当作这样做的借口。

例如,给出以下代码:

 int scaled_velocity(int v, unsigned char pow) { if (v > 250) v = 250; if (v < -250) v = -250; return v << pow; } 

一个二进制补码的实现将不必花费任何努力来把这个expression式作为一个二补码转换,而不考虑v是正还是负。

然而,今天一些编译器编写者偏好的理念是,如果程序要进入未定义行为, v只能是负数,那么没有理由让程序截取v的负数范围。 尽pipe负值的左移曾经被每一个重要的编译器所支持,而大量现有的编码依赖于这种行为,但现代哲学将解释这样一个事实,即标准说左移负值是UB这意味着编译器作者应该随时忽略这一点。

实现定义 –

实现者希望,应该有充分的文件,标准给出select,但肯定要编译

未指定 –

与实现定义相同,但没有logging

Undefined-

任何事情都可能发生,照顾它。

C ++标准n3337§1.3.10 实现定义的行为

行为,对于一个结构良好的程序构造和正确的数据,依赖于实现和每个实现文件

有时候,C ++标准并没有对某些结构强加特定的行为,而是说特定的,明确的行为必须由特定的实现(库的版本)来select和描述 。 所以即使标准没有描述,用户仍然可以确切地知道程序的行为。


C ++标准n3337§1.3.24 未定义的行为

本标准对本标准没有要求的行为[注:当本标准忽略任何行为的明确定义或程序使用错误的结构或错误的数据时,可能会出现未定义的行为。 允许的未定义的行为范围从完全忽略情况,以不可预知的结果,在翻译或程序执行期间以文档化的方式performance环境特征(不论是否发布诊断消息),终止翻译或执行(发行的诊断消息)。 许多错误的程序结构不会产生未定义的行为; 他们需要被诊断。 – 结束注意]

当程序遇到没有按照C ++标准定义的构造时,它可以做任何想做的事情(可能发邮件给我,也可能发邮件给你,也可能完全忽略代码)。


C ++标准n3337§1.3.25 未指定的行为

行为,对于格式良好的程序构造和正确的数据,取决于实现[注意:实现不需要logging哪些行为发生。 可能的行为范围通常由本国际标准划定。 – 结束注意]

C ++标准并没有在某些结构上施加特定的行为,而是说特定的实现(库的版本)必须select一个特定的,明确定义的行为( 不必描述 )。 所以在没有提供描述的情况下,用户可能很难确切知道程序的行为。

Interesting Posts