什么是C 和 C++ 中的 未定义行为(UB)? 未指定的行为 和 实现定义的 行为呢?它们之间有什么区别?
未定义的行为 是 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 段,它调用 未定义的行为 :
p
"hello!\n"
尝试修改字符串文字的效果是未定义的。
我可以听到人们尖叫“但是等等,我可以编译这个没有问题并得到输出yellow”或“你是什么意思未定义,字符串文字存储在只读内存中,所以第一次分配尝试导致核心转储”。这正是未定义行为的问题。基本上,一旦您调用未定义的行为(甚至是鼻恶魔),该标准允许任何事情发生。如果根据你的语言心理模型存在“正确”的行为,那么该模型就是错误的;C++ 标准拥有唯一的投票权,句号。
yellow
未定义行为的其他示例包括访问超出其边界的数组、取消引用空指针、在对象的生命周期结束后访问对象或编写据称聪明的表达式,如i+++ ++i.
i+++ ++i
C++ 标准的 1.9 节还提到了未定义行为的两个不太危险的兄弟, 未指定行为 和 实现定义行为 :
本国际标准中的语义描述定义了一个参数化的非确定性抽象机。 抽象机的某些方面和操作在本国际标准中描述为 实现定义 (例如,sizeof(int))。这些构成了抽象机的参数。每个实现都应包括描述其在这些方面的特征和行为的文档。 抽象机的某些其他方面和操作在本国际标准中描述为 未指定 (例如,函数参数的评估顺序)。在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机器的不确定性方面。 本国际标准中将某些其他操作描述为 未定义 (例如,取消引用空指针的效果)。[ 注 : 本国际标准对包含未定义行为的程序的行为没有任何要求。 ” 尾注 ]
本国际标准中的语义描述定义了一个参数化的非确定性抽象机。
抽象机的某些方面和操作在本国际标准中描述为 实现定义 (例如,sizeof(int))。这些构成了抽象机的参数。每个实现都应包括描述其在这些方面的特征和行为的文档。
sizeof(int)
抽象机的某些其他方面和操作在本国际标准中描述为 未指定 (例如,函数参数的评估顺序)。在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机器的不确定性方面。
本国际标准中将某些其他操作描述为 未定义 (例如,取消引用空指针的效果)。[ 注 : 本国际标准对包含未定义行为的程序的行为没有任何要求。 ” 尾注 ]
具体来说,第 1.3.24 节规定:
允许的未定义行为的范围从 完全忽略具有不可预测结果的情况 ,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(有发出的诊断消息)。