什么是 C 和 C++ 中的 未定义行为(UB)? 未指定的行为 和 实现定义的 行为呢?它们之间有什么区别?
原文由 Zolomon 发布,翻译遵循 CC BY-SA 4.0 许可协议
什么是 C 和 C++ 中的 未定义行为(UB)? 未指定的行为 和 实现定义的 行为呢?它们之间有什么区别?
原文由 Zolomon 发布,翻译遵循 CC BY-SA 4.0 许可协议
从历史上看,实现定义的行为和未定义的行为都代表了这样一种情况,标准的作者期望编写高质量实现的人会使用判断来决定哪些行为保证(如果有的话)对在预期应用程序领域中运行的程序有用。预期目标。高端数字运算代码的需求与低级系统代码的需求大不相同,UB 和 IDB 都为编译器编写者提供了满足这些不同需求的灵活性。这两个类别都没有要求实现的行为方式对任何特定目的有用,甚至对任何目的都有用。然而,声称适用于特定目的的质量实现, _无论标准是否要求_,都应该以符合该目的的方式行事。
实现定义的行为和未定义的行为之间的唯一区别在于,前者要求实现定义和记录一致的行为 _,即使在实现可能没有任何用处的情况下也是如此_。它们之间的分界线不是定义行为的实现通常是否有用(无论标准是否要求,编译器编写者都应该在实际情况下定义有用的行为),而是 _是否可能存在定义行为同时代价高昂的实现而且没用_。对此类实现可能存在的判断并不以任何方式、形式或形式暗示对支持在其他平台上定义的行为的有用性的任何判断。
不幸的是,自 1990 年代中期以来,编译器编写者已经开始将缺乏行为要求解释为一种判断,即即使在至关重要的应用领域,甚至在几乎没有成本的系统上,行为保证也不值得付出代价。编译器作者没有将 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 解释为暗示编译器编写者应该随意忽略这一点。
原文由 supercat 发布,翻译遵循 CC BY-SA 3.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.4k 阅读
1 回答1.6k 阅读✓ 已解决
未定义的行为 是 C 和 C++ 语言的那些方面之一,可能会让来自其他语言的程序员感到惊讶(其他语言试图更好地隐藏它)。基本上,即使许多 C++ 编译器不会报告程序中的任何错误,也可以编写行为无法预测的 C++ 程序!
我们来看一个经典的例子:
变量
p
指向字符串文字"hello!\n"
,下面的两个赋值尝试修改该字符串文字。这个程序有什么作用?根据 C++ 标准的第 2.14.5 节第 11 段,它调用 _未定义的行为_:我可以听到人们尖叫“但是等等,我可以编译这个没问题并得到输出
yellow
”或“你是什么意思未定义,字符串文字存储在只读内存中,所以第一次赋值尝试导致核心转储”。这正是未定义行为的问题。基本上,一旦您调用未定义的行为(甚至是鼻恶魔),该标准就允许任何事情发生。如果根据您的语言心理模型存在“正确”行为,那么该模型就是错误的; C++ 标准拥有唯一的投票权,句号。未定义行为的其他示例包括访问超出其边界的数组、 取消引用空指针、 在其生命周期结束后访问对象 或编写 据称聪明的表达式,如
i++ + ++i
。C++ 标准的 1.9 节还提到了未定义行为的两个不太危险的兄弟, 未指定行为 和 实现定义的行为:
具体来说,第 1.3.24 节规定:
您可以做些什么来避免遇到未定义的行为?基本上,你必须阅读那些知道他们在说什么的作者写的 好的 C++ 书籍。避免使用互联网教程。避免公牛。