除了未定义、未指定和实现定义的行为之外,C++26 还引入了错误行为(请参阅)。这种新的构造与现有的行为有何不同?为什么将其添加到 C++ 标准中?
7
1 个回答
1
错误行为是“有缺陷的”或“不正确的”行为,如所解释的那样。该提案将错误行为引入了 C++26,将之前未定义的行为转变为错误行为。
最显着的区别是,未定义的行为对于程序可以执行的操作没有限制,包括跳转到“随机”函数、访问不应访问的内存以及其他不利于安全的影响。错误行为的形式是([defns.erroneous]):
建议实施来诊断的明确定义的行为
可以通过警告、运行时错误等来诊断;正式地,[intro.abstract] p4.1.2 解释说:
如果执行包含指定为具有错误行为的操作,则允许该实现发出诊断并允许在该操作之后的未指定时间终止执行。
动机
不幸的是,大量的 C++ 代码并非没有错误,并且许多错误可能会危害安全。一个明显的例子是这样的:
void (*f)(); // uninitialized function pointer;
// basically an abstraction for an instruction address
// ...
f(); // what address do we jump to?
如果f
占用堆栈上的一些空间,攻击者可以确保在执行此代码之前堆栈上的内存具有他们选择的值。
f()
因此,攻击者可以跳转到他们想要的程序中的任何指令。的此类案例还有很多。
简单地通过默认初始化函数指针来使这段代码“正确”nullptr
也是没有意义的,因为这里显然存在一个错误。
f
应该已经初始化,如果我们在调用之前忘记初始化它,将其存储在某个地方等,编译器应该让我们注意这一事实。f
我们不希望这个错误只是“被掩盖”。
错误的行为是如何发生的?
错误的行为始于错误的值,例如,当变量未初始化时会产生错误的值。附带说明一下,可以使用以下属性重现 C++26 之前的行为[[indeterminate]]
:
void f(int);
int indet [[indeterminate]]; // indet has indeterminate value
int erron; // erron has erroneous value ([basic.indet])
f(indet); // undefined behavior
f(erron); // erroneous behavior
如上所述,未定义的行为可以在这里执行任何操作,包括跳转到 以外的函数f
,而f(erron)
应该始终具有已定义的行为,但应该在某个时刻进行诊断。
错误与格式错误
程序类似,因为两者都应该导致诊断(另请参见)。
然而,错误行为在程序执行期间生效,而程序在翻译(编译)期间格式错误。例如:
int x = float; // ill-formed; not valid C++ code,
// shall be diagnosed
int y; // well-formed (valid C++ code) but y has erroneous value
int z = y; // erroneous behavior, should be diagnosed
常量表达式中的错误行为
与未定义的行为不同,错误的行为总是使表达式失去常量表达式 ([expr.const]) 的资格。请注意,未定义的行为在大多数情况下表现相同,但例如大多数标准库中失败的Precondition[[assume]]
或失败的属性仍然可能导致常量表达式内部出现 UB。
此外,constexpr
对象不能有错误的值:
constexpr int x; // error: x has erroneous value
在 C++23 中,这也是格式错误的,因为x
它具有不确定的值。
更广阔的前景
总的来说,C++ 开发人员和 C++ 委员会正在推动该语言向“更安全”的方向发展。作为其中的一部分,未来几年大量未定义的行为可能会变成错误的行为。
在某些情况下,已经有一个非常积极的提案,例如。其他一些未定义行为的情况(例如有符号整数溢出、除以零等)可能会出错。
难以诊断的 UB 形式,例如数据争用或无效的向下转换(使用static_cast
)可能会保持未定义状态,甚至可能无限期地保持不变。
错误行为的代价
编译器越来越依赖未定义的行为来达到优化的目的。例如:
void f(int i) {
int arr[1] { 123 };
return arr[i];
}
编译器可以将其优化为:
void f(int):
mov eax, 123
ret
如果i
是 以外的任何值0
,则该数组arr
将被越界访问,这是未定义的行为。编译器可以假设 UB 根本不会发生并进行相应的优化。如果越界访问数组变成了错误行为,则鼓励编译器向数组访问添加运行时边界检查,如果i
不是则终止程序0
。
总之,错误行为不是“自由的”;而是“自由的”。它是以性能为代价的。错误行为通常被添加到 C++ 标准中,其中未定义行为的安全风险很大,并且未定义行为的原因通常不用于优化。
14
-
“建议实施诊断的明确定义的行为..”因此与格式不正确相同,只是标准只是将“必需”一词更改为“推荐”。
– -
1@Alan我添加了一个部分来讨论格式不正确的程序和错误行为之间的区别。
– -
我懂了。但为什么在没有使用 odr 的情况下会出现
int y;
错误呢? “错误值”也与“不确定值”相同y
吗?
–
-
1@Alan它有错误的值,这还不是错误的行为,但是例如这样做
int z = y;
将是错误的行为。我已经更新了示例来展示这一点。错误值与不确定值不同;在大多数情况下使用不确定的值会导致未定义的行为,而使用错误的值会导致错误的行为。
–
-
1“鼓励编译器对数组访问添加运行时边界检查,如果 i 不为 0,则终止程序。” – 您在论文中的哪个位置看到错误行为应导致终止(或边界检查)的建议?
–
|
–
–
–
–
–
|