什么是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 ++书籍。螺丝互联网教程。螺丝废话。
这是合并产生的一个奇怪的事实,即此答案仅涵盖C ++,但此问题的标记包括C。C具有“未定义行为”的不同概念:即使行为也被声明为,它仍将要求实现提供诊断消息。对于某些规则违规(约束违规)未定义。
@Benoit这是未定义的行为,因为标准表示它是未定义的行为,即周期。在某些系统上,实际上字符串文字存储在只读文本段中,如果尝试修改字符串文字,程序将崩溃。在其他系统上,字符串文字确实会出现变化。该标准未规定必须执行的操作。这就是未定义行为的意思。
@FredOverflow,为什么一个好的编译器允许我们编译具有未定义行为的代码?编译这种代码究竟有什么好处?当我们尝试编译具有未定义行为的代码时,为什么所有好的编译器都没有给我们一个巨大的红色警告信号?
@Pacerier有些事情在编译时是不可检查的。例如,并非总是可以保证永远不会取消引用空指针,但这是未定义的。
@Celeritas,未定义的行为可能是不确定的。例如,不可能提前知道未初始化存储器的内容是什么,例如。
int f(){int a; return a;}
:的值a
可能在函数调用之间改变。