Zol*_*mon 508 c c++ undefined-behavior unspecified-behavior implementation-defined-behavior
C和C++中未定义,未指定和实现定义的行为有什么区别?
fre*_*low 384
未定义的行为是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;
}
Run Code Online (Sandbox Code Playgroud)
变量p
指向字符串文字"hello!\n"
,下面的两个赋值尝试修改该字符串文字.这个程序做什么用的?根据C++标准的第2.14.5节第11段,它调用未定义的行为:
尝试修改字符串文字的效果是未定义的.
我可以听到人们尖叫"但是等等,我可以编译这个没问题并得到输出yellow
"或"你的意思是什么未定义,字符串文字存储在只读内存中,所以第一次分配尝试会导致核心转储".这正是未定义行为的问题.基本上,一旦你调用未定义的行为(甚至是鼻子恶魔),标准允许任何事情发生.如果根据您的语言心理模型存在"正确"行为,那么该模型就是错误的; C++标准有唯一的投票期.
未定义行为的其他例子包括访问超出其边界的数组,解引用空指针,访问对象后,他们的寿命结束或写入据说聪明的表情一样i++ + ++i
.
C++标准的第1.9节还提到了未定义行为的两个不太危险的兄弟,未指定的行为和实现定义的行为:
本国际标准中的语义描述定义了参数化的非确定性抽象机器.
抽象机的某些方面和操作在本国际标准中描述为实现定义的(例如
sizeof(int)
).这些构成了抽象机器的参数.每个实施应包括描述其在这些方面的特征和行为的文件.抽象机器的某些其他方面和操作在本国际标准中被描述为未指定的(例如,对函数的参数的评估顺序).在可能的情况下,本国际标准定义了一组允许的行为.这些定义了抽象机器的非确定性方面.
本国际标准中将某些其他操作描述为未定义(例如,取消引用空指针的效果).[ 注意:本国际标准对包含未定义行为的程序的行为没有要求.- 结束说明 ]
具体而言,第1.3.24节规定:
允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定行为,终止翻译或执行(发布时)一条诊断信息).
你能做些什么来避免遇到未定义的行为?基本上,你必须阅读那些了解他们所谈论内容的作者的优秀C++书籍.螺丝网络教程.螺旋公牛队.
AnT*_*AnT 93
嗯,这基本上是标准的直接复制粘贴
3.4.1 1 实现定义的行为未指定的行为,其中每个实现记录了如何进行选择
2示例实现定义的行为的示例是当有符号整数向右移位时高阶位的传播.
3.4.3 1 使用不可移植或错误的程序结构或错误数据时的未定义行为行为,本国际标准不对此要求
2注意可能的未定义行为包括完全忽略不可预测结果的情况,以及以环境特征(有或没有发出诊断消息)的文档化方式执行转换或程序执行,终止翻译或执行(带有发出诊断信息).
3示例未定义行为的示例是整数溢出的行为.
3.4.4 1 未指明的行为使用未指定的值,或本国际标准提供两种或更多种可能性的其他行为,并且在任何情况下都不会对其进行任何进一步的要求
2示例未指定行为的示例是评估函数参数的顺序.
Ara*_*raK 56
也许简单的措辞可以比标准的严格定义更容易理解.
实现定义的行为
语言表示我们有数据类型.编译器供应商指定他们使用的大小,并提供他们所做的文档.
未定义的行为
你做错了什么.例如,您有一个非常大的值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?
Run Code Online (Sandbox Code Playgroud)
该语言未指定评估,从左到右或从右到左!因此,未指定的行为可能会或可能不会导致未定义的行为,但当然您的程序不应产生未指定的行为.
@eSKay我认为你的问题值得编辑答案澄清更多:)
for
fun(fun1(), fun2());
是不是"实现定义"的行为?毕竟编译器必须选择一个或另一个课程?
实现定义和未定义之间的区别在于编译器应该在第一种情况下选择行为,但在第二种情况下不需要.例如,实现必须只有一个定义sizeof(int)
.因此,它不能说sizeof(int)
程序的某些部分为4,其他部分为8.与未指定的行为不同,编译器可以说OK,我将从左到右评估这些参数,并且从右到左评估下一个函数的参数.它可能发生在同一个程序中,这就是为什么它被称为未指定的原因.实际上,如果指定了一些未指定的行为,C++可能会变得更容易.看看Stroustrup博士对此的回答:
据称,为编制者提供这种自由所需的内容与要求"普通的从左到右的评估"之间的差异可能很大.我不相信,但是有无数的编译器"在那里"利用自由和一些人热情地捍卫自由,改变将是困难的,可能需要数十年才能渗透到C和C++世界的遥远角落.我很失望并非所有编译器都会对++ i + i ++等代码发出警告.同样,参数的评估顺序是未指定的.
IMO太多"事物"未定义,未指定,实现定义等.但是,这很容易说,甚至可以提供示例,但很难修复.还应该注意,避免大多数问题并产生可移植代码并不是那么困难.
Joh*_*itb 26
来自官方C理由文件
术语未指定的行为,未定义的行为和实现定义的行为用于对编写程序的结果进行分类,这些程序的属性标准不能或不能完全描述.采用这种分类的目的是允许实现中的某种变化,这允许实现的质量成为市场中的主动力量以及允许某些流行的扩展,而不去除与标准的一致性的标记.标准的附录F对属于这三个类别之一的行为进行了编目.
未指定的行为使实现者在翻译程序时具有一定的自由度.只要没有翻译程序,这个范围就不会延伸.
未定义的行为使实现者许可证不会捕获难以诊断的某些程序错误.它还标识了可能符合语言扩展的区域:实现者可以通过提供正式未定义行为的定义来扩充语言.
实现定义的行为使实现者可以自由选择适当的方法,但需要向用户解释此选择.指定为实现定义的行为通常是用户可以基于实现定义做出有意义的编码决策的行为.在决定实施定义应该有多广泛时,实施者应该牢记这个标准.与未指定的行为一样,只是无法转换包含实现定义的行为的源不是一个充分的响应.
And*_*bel 10
未定义的行为与未指定的行为有一个简短的描述.
他们的最后总结:
总而言之,除非您的软件需要便携,否则通常您不应该担心这些行为.相反,未定义的行为总是不受欢迎的,永远不应该发生.
从历史上看,实现定义行为和未定义行为都代表了标准作者期望编写高质量实现的人会使用判断来决定哪些行为保证(如果有的话)对于在其上运行的预期应用领域中的程序有用的情况.预定目标.高端数字运算代码的需求与低级系统代码的需求大不相同,UB和IDB都为编译器编写者提供了满足这些不同需求的灵活性.这两个类别都没有规定实现的行为方式对任何特定目的有用,甚至出于任何目的.然而,声称适合特定目的的质量实施应该以符合此目的的方式运行,无论标准是否需要.
实现定义行为和未定义行为之间的唯一区别是,前者要求实现定义并记录一致的行为,即使在实现可能没有任何帮助的情况下也是如此.它们之间的分界线不是它是否通常对实现定义行为有用(编译器编写者应该在实际时定义有用的行为,无论标准是否要求它们),但是是否可能存在定义行为同时代价高昂的实现和无用的.判断这些实现可能不存在任何方式,形式或形式,暗示对支持其他平台上定义的行为的有用性的任何判断.
不幸的是,自20世纪90年代中期以来,编译器编写者开始将缺乏行为规定解释为行为保证即使在它们至关重要的应用领域也不值得花费的判断,甚至在他们几乎没有成本的系统上也是如此.编译器编写者不是将UB视为执行合理判断的邀请,而是将其视为不这样做的借口.
例如,给出以下代码:
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
Run Code Online (Sandbox Code Playgroud)
一个二进制补码的实现不必花费任何努力将该表达式v << pow
视为二进制补码移位而不考虑v
是正还是负.
然而,当今一些编译器编写者的首选哲学意味着,v
如果程序要进行未定义的行为,那么只能是负面的,没有理由让程序剪辑为负范围v
.尽管负值的左移过去常常被支持在每一个有意义的编译器上,并且大量现有代码依赖于这种行为,但现代哲学会解释标准说左移负值是UB的事实.暗示编译器编写者应该随意忽略它.
实施定义 -
实施者希望,应该有详细记录,标准给出选择,但肯定编译
未指明 -
与实现定义相同但未记录
Undefined-
任何事情都可能发生,照顾它.
C ++标准n3337 § 1.3.10 实现定义的行为
行为,对于格式正确的程序构造和正确数据,取决于实施以及每个实施文档
有时,C ++ Standard并未在某些构造上强加特定的行为,而是说必须通过特定的实现(库版本)来选择和描述特定的,定义明确的行为。因此,即使Standard并未对此进行描述,用户仍然可以确切知道程序的行为方式。
C ++标准n3337 § 1.3.24 未定义行为
本国际标准不施加任何要求的行为[注:当本国际标准省略行为的任何明确定义或程序使用错误的构造或错误的数据时,可能会出现未定义的行为。允许的不确定行为包括:完全忽略具有无法预测结果的情况,以环境特征的书面形式在翻译或程序执行期间的行为(带有或不带有诊断消息),终止翻译或执行(带有发行)诊断消息)。许多错误的程序构造不会引起未定义的行为。他们需要被诊断。—尾注]
当程序遇到未按照C ++标准定义的构造时,它可以做任何想做的事情(可以给我发送电子邮件,或者给你发送电子邮件,或者完全忽略代码)。
C ++标准n3337 § 1.3.25 未指定的行为
行为,对于格式正确的程序构造和正确数据,取决于实现[注:不需要实现来记录发生哪种行为。可能的行为范围通常由本国际标准来描述。—尾注]
C ++ Standard并未在某些构造上强加特定的行为,而是说必须通过特定的实现(库的版本)选择特定的,定义明确的行为(无需描述bot)。因此,在没有提供任何描述的情况下,用户可能很难确切知道程序的行为方式。
归档时间: |
|
查看次数: |
51330 次 |
最近记录: |