未定义、未指定和实现定义的行为
-
25-09-2019 - |
题
C 和 C++ 中的未定义行为是什么?未指定的行为和实现定义的行为又如何呢?它们之间有什么区别?
解决方案
未定义的行为 是 C 和 C++ 语言中可能令来自其他语言的程序员感到惊讶的方面之一(其他语言试图更好地隐藏它)。基本上,即使许多 C++ 编译器不会报告程序中的任何错误,也可能编写不以可预测方式运行的 C++ 程序!
我们来看一个经典的例子:
#include <iostream>
int main()
{
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
变量 p
指向字符串文字 "hello!\n"
, ,下面的两个赋值尝试修改该字符串文字。这个程序有什么作用?根据 C++ 标准第 2.14.5 节第 11 段,它调用 未定义的行为:
尝试修改字符串文字的效果是未定义的。
我可以听到人们尖叫“但是等等,我可以毫无问题地编译并获得输出 yellow
”或“未定义是什么意思,字符串文字存储在只读内存中,因此第一次赋值尝试会导致核心转储”。这正是未定义行为的问题。基本上,一旦您调用未定义的行为(甚至鼻恶魔),该标准就允许任何事情发生。如果根据你的语言心理模型存在“正确”行为,那么该模型就是错误的;C++ 标准拥有唯一的投票权。
未定义行为的其他示例包括访问超出其范围的数组, 取消引用空指针, 在对象的生命周期结束后访问对象 或写作 据称巧妙的表达 喜欢 i++ + ++i
.
C++ 标准的 1.9 节还提到了未定义行为的两个不太危险的兄弟, 未指定的行为 和 实现定义的行为:
本国际标准中的语义描述定义了参数化的非确定性抽象机。
抽象机的某些方面和操作在本国际标准中描述为 实现定义的 (例如,
sizeof(int)
)。这些构成了抽象机的参数。每个实现都应包括描述其在这些方面的特征和行为的文档。抽象机的某些其他方面和操作在本国际标准中描述为 未指定 (例如,函数参数的求值顺序)。在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机的非确定性方面。
本国际标准中将某些其他操作描述为 不明确的 (例如,取消引用空指针的效果)。[ 笔记: 本国际标准对包含未定义行为的程序的行为没有强加任何要求。 —尾注 ]
具体而言,第 1.3.24 节规定:
允许的未定义行为范围为 完全无视情况,结果不可预测, ,在翻译或程序执行期间以环境特征的记录方式表现(发出或不发出诊断消息),终止翻译或执行(发出诊断消息)。
您可以做什么来避免遇到未定义的行为?基本上,你必须阅读 不错的C++书籍 作者知道自己在说什么。螺丝互联网教程。螺丝牛。
其他提示
那么,这基本上是一个直接从标准拷贝粘贴
<强> 3.4.1 强> 1的实现定义强>未指定的行为,其中 每个实现文件如何 作出选择
2实施例的示例 实现定义的行为是 高阶的传播位当 有符号整数右移。
<强> 3.4.3 强> 1的未定义的行为强>行为,在使用非便携式的或错误的 程序构建体或错误的 数据,此国际 标准并没有规定要求
2 注意可能的不确定的行为 从忽略的情况范围 完全不可预知的结果, 在翻译的过程或行为 程序执行在形成文件的 的方式特性 环境(有或无 发行诊断消息的),以 终止翻译或执行 (与发行的诊断的 消息)。
3实施例的实例 未定义行为是行为 整数溢出。
<强> 3.4.4 强> 1的未指定的行为使用未指定的值的,或其他行为 当本标准 提供了两个或更多的可能性,并 规定了没有进一步的要求 这是选择在任何实例
2 的未指定的实施例的示例 行为是为了在其中 参数的函数进行评估。
也许容易措词可以比该标准的严格的定义更容易理解。
<强>实现定义强>结果 语言说,我们有数据类型。编译器厂商指定大小应他们的使用,并提供他们做了什么文档。
<强>未定义的行为强>结果
你正在做的事情是错误的。例如,你必须在不适合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());
是不 行为“实现定义”? 编译器必须选择一个或 其他过程中,毕竟?
之间实现定义和指定,是编译器应该接在所述第一情况下的行为,但它不必在第二种情况下的差别。例如,一个实现必须有且只有一个sizeof(int)
的定义。所以,不能说sizeof(int)
是4对节目的一部分和8人。与未指定的行为,在编译器可以说OK我要去评估这些参数左到右,下一个函数的参数进行评估从右到左。它可以在同一个程序发生,这就是为什么它被称为 未指定的。事实上,C ++可能已经作出,如果一些非特定行为规定了更容易。下面一起来看看博士Stroustrup的答案为:
它被要求的差 之间有什么方法可以使生产 编译器这种自由和 要求“普通左到右 评价”可以显著。我 不服气,但无数 编译器“那里”趁着 自由和一些人的 热情捍卫的自由,一个 改变是困难的,并可能 要几十年才能渗透到 C和C的最远角落++ 世界。我失望的是,并不是所有的 编译器警惕代码如 ++我+ I ++。同样,论证评价的顺序是 未指定的。
IMO太多太多的“东西”是左 不确定的,不确定, 实现定义等等。然而, 这说起来容易,甚至给 的例子,但很难解决。它 还应该指出,这是不 所有难以避免的大部分 存在的问题和产生便携 代码。
从官方C原理文献
术语不确定行为,未定义行为,和实现定义行为被用来分类写程序其属性的标准的结果没有,或者不能完全形容。采用该分类的目标是允许实现中的特定品种,其允许执行质量是在市场上的主动力以及允许某些流行的扩展,而不去除一致性的扁囊剂为标准。附录F为标准编目那些落入这三个类别中的一个行为。
未指定的行为给出了实现者在平移方案的一些余地。这个纬度不延伸远没有翻译的程序。
未定义行为的给实施者许可证没有赶上那难以诊断的某些程序错误。它还确定可能符合语言扩展的领域:实现者可以通过提供正式未定义行为的定义扩大的语言。
实现定义行为给出了一个实现者可以自由地选择合适的方法,但是需要这种选择说明给用户。指定行为作为实现定义通常是那些在用户可以使基础上,实现定义有意义的编码判决。决定一个实现定义应该如何广泛的是,当执行者应该牢记这个标准。与未指定的行为,只是不能平移含有实现定义的源不是充分的反应。
从历史上看,无论是实现定义和未定义行为代表了该标准的作者预期,人们书写质量的实现会用判决来决定什么行为保证,如果有的话,会在预期的应用领域的方案是有用的情况下,在预定的目标运行。高端数字压缩编码的需求来自那些低级别的系统代码完全不同的,UB和美洲开发银行都给予编译器作者的灵活性,以满足这些不同的需求。无论是类强制要求实施的行为在某种程度上这是有用的任何特定用途,甚至用于任何目的。质量实现,如权利要求以适合于特定用途,但是,应该表现在相称这样的目的的的方式标准是否需要与否
实现定义的操作和未定义行为之间的唯一区别是,前者需要实现定义和记录一致的行为的甚至在情况下,没有什么可以做的可能的实施将是有益的。它们之间的分界线不是否会通常是有益的实施方式中,以限定行为(实用时是否标准要求它们或不编译器作者应该定义有用的行为),但是否有可能是,其中限定了行为将实施方式中是同时昂贵和无用。这样的实施方式中可能存在的判决不以任何方式,形状或形式,暗示关于在其他平台上支撑一定义的行为的有用的任何判决。
不幸的是,自20世纪90年代中期编译器的编写者已经开始解释缺乏行为任务作为一个判断,行为担保是不值得的成本,即使在应用领域,他们是至关重要的,甚至他们几乎花费系统没有。相反治疗UB作为邀请作出合理的判断,编译器的编写者已经开始把它当作借口的不的这样做。
例如,考虑下面的代码:
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
一个二进制补码的实施将不必花费任何力气
任何治疗的表达v << pow
作为二进制补码移位
而不考虑v
是否正或负的方面。
中的一些今天的编译器编写者的首选理念,但会建议,因为v
只能是负数,如果程序将从事未定义行为,没有理由有计划夹v
的负范围。尽管左移用在重要的每一个编译器支持负值的,以及大量的现有代码依赖于这种行为,现代哲学会解释,标准说,左移负值是UB的事实这意味着编译器作者应该随意忽略。
实施defined-
实现者的愿望,应该做到有据可查,标准给出的选择,但一定要编译
未知 -
同实现定义的但没有记录
未定义 -
任何可能发生的事情,它的照顾。
C ++标准n3337的§强> 1.3.10 的实现定义强>
行为,对于形成阱程序构建体和正确的数据,那 依赖于实现,并且每个执行文件
有时C ++标准没有对一些构建特定的行为,但表示而不是特定的,明确定义的行为必须被选择和描述按特定实现(库的版本)。所以,用户仍然可以确切地知道如何将编程尽管标准没有描述这样的行为。
C ++标准n3337的§强> 1.3.24 的未定义的行为强>
行为指此国际标准规定的要求没有 [注:未定义行为可预期,当此国际 标准忽略了行为的任何明确的定义或当一个程序 使用错误的构建体或错误数据。允许未定义 从完全无视局势的行为范围 不可预知的结果,到做人翻译或程序中 执行环境中的记录方式的特征 (具有或不发出一个诊断消息的),以终止 翻译或执行(与发行的诊断的 信息)。许多错误的程序结构不使人产生未定义 行为;他们需要进行诊断。 - 注完]
当是根据C ++标准没有定义程序遇到结构允许它做任何它想做的事(或许发邮件给我或也许发送电子邮件到你也许完全忽略的代码)。
C ++标准n3337的§强> 1.3.25 的未指定的行为强>
行为,对于形成阱程序构建体和正确的数据,那 依赖于实现[注:执行不 需要发生行为文档。可能的范围 行为通常是由该国际标准划定。 - 结束 音符]
C ++标准没有规定特定的行为上的一些构建体,但表示而不是特定的,明确定义的行为必须被选择(的机器人没有必要描述强>)由特定的实现(库的版本)。所以,当已提供任何说明的情况下,可能难以向用户确切地知道如何将程序的行为。
有哪些应该表现在有用的和可预测的方式在某些情况下有许多结构,但实际上不能进行在所有实现所有的情况下这样做。通常情况下,一组的情况下对其中的结构应该是可用将取决于目标平台和应用领域上。由于实施了不同的目标和领域应该处理组不同的情况下,标准视图,其案件处理,因为执行问题的质量问题。此外,由于标准的作者认为没有必要禁止从是这样的,质量低劣的“符合”的实现为是无用的,他们往往也懒得明确授权的情况下,他们的行为预期,所有非垃圾实现,以支持即使没有授权。
例如,代码:
struct foo {int x;} = {0};
int main(void)
{
foo.x = 1;
return foo.x-1;
}
使用类型int
的左值[即foo.x
]访问类型struct foo
的对象的所存储的值,即使N1570 6.5p7包含任何将允许除了通过类型struct foo
的左值或字符类型的左值将被访问类型struct foo
的目的,也没有在标准包含可能豁免的6.5p7要求结构成员访问表达式的任何语言。
显然不能处理简单结构成员访问表达式任何编译器应该被看作是异常低质量的,可能不适合于任何东西。因此,它应该是合理的期望,任何寻求以生产高质量的实施将支持这样的结构,无论在标准规定它是否的。只要编译器作者可以信任赚了善意的努力,生产出优质的编译器适合于他们的预期目的,并开放有关的宗旨,为它自己的编译器或不适合,那就没有理由有标准的废墨试图状态的东西,应该是显而易见的。里面应该有可用的和可预测的行为,许多动作,其实,未定义行为,因为标准的作者值得信赖的编译器作家作出合理的判断,而不是使用的事实,以此为借口的行为发生未定义行为扔判断窗外