C++26:标准库强化
摘要
C++26 引入了标准化的库强化机制,用于在运行时捕获常见的未定义行为(如越界访问)。基于 Google 的生产经验,此举仅带来 0.30% 的性能开销,同时将段错误减少了 30%。
<p><a href="https://lobste.rs/s/5xg2wq/c_26_standard_library_hardening">评论</a></p>
查看缓存全文
缓存时间: 2026/05/13 16:18
# C++26: 标准库强化 来源:https://www.sandordargo.com/blog/2026/05/13/cpp26-library-hardening C++ 中的未定义行为(UB)是最难处理的 bug 类别之一。它会静默地破坏内存、导致远离实际错误位置的崩溃,或者最糟糕的是——恰好在你的机器上正常工作。在实际代码库中,相当一部分 UB 并非来自于奇特的语言特性,而是源于对标准库的基本误用:越界访问 `vector`、对空容器调用 `front()`,或解引用空的 `optional`。C++26 通过标准库强化直接解决了这个问题,该特性由 P3471R4 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html) 引入。
## 什么是库强化?
库强化将标准库中的某些未定义行为转化为运行时可检测的契约违反。当强化的前置条件被违反时,运行时会在任何其他可观察副作用发生之前做出反应——可以将其视为为今天属于 UB 的操作添加边界检查。
这并不是一个新想法。三大主流标准库实现已经提供了各自厂商特定的强化模式。问题在于这些机制各不相同、不可移植且规范不一致。P3471R4 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html) 将实现已有的做法标准化。标准化的强化版本成为了契约(在 P2900 (https://isocpp.org/files/papers/P2900R6.pdf) 中规定)的首次实际应用。这与语言的发展方向完美契合。
## 动机:来自实际生产环境的证据
支持此特性的最强论据来自谷歌的生产经验,该提案引用 (https://security.googleblog.com/2024/11/retrofitting-spatial-safety-to-hundreds.html):在“数亿行 C++ 代码”中应用强化的 libc++ 发现了超过 1000 个 bug,包括安全关键型 bug。平均性能开销低得惊人,仅 **0.30%**——千分之三。这一开销之所以能降低,得益于编译器在优化过程中消除冗余检查的能力。其影响超越了安全性:团队观察到生产环境中**基线段错误率降低了 30%**,这表明代码正确性得到了全面改善。这是多么了不起的成果!
## 哪些操作会被强化?
该提案有意识地只关注*内存安全前置条件*。检查所有前置条件是一个“明确非目标”——目标是以低廉的成本捕获高价值的检查。
### `std::span`
- `operator[]`:要求 `idx < size()`
- `front()`、`back()`:要求非空
- `first()`、`last()`、`subspan()`:验证 `count` 和 `offset` 在边界内
- 从范围构造的构造函数:验证长度匹配
### `std::string_view`
- `operator[]`:要求 `pos < size()`
- `front()`、`back()`:要求非空
- `remove_prefix()`、`remove_suffix()`:要求 `n <= size()`
### 序列容器 (`vector`、`deque`、`list`、`forward_list`、`array`、`string`)
- `operator[]`:要求 `n < size()`(对于 `basic_string`,由于访问空终止符是有效的,因此要求 `n <= size()`)
- `front()`、`back()`:要求 `!empty()`
- `pop_front()`、`pop_back()`:要求 `!empty()`
### `std::optional` 和 `std::expected`
- `optional` 上的 `operator->()`、`operator*()`:要求 `has_value()`
- `expected` 上的值访问:要求 `has_value()`
- `expected` 上的 `error()`:要求 `!has_value()`
### `std::mdspan`
- `operator[]`:验证所有多维索引都在其范围内
- 构造函数:验证转换期间的静态范围匹配
### `std::bitset` 和 `std::valarray`
- `operator[]`:要求 `pos < size()` / `n < size()`
有些操作*有意*被排除在外。基于迭代器的操作如 `erase()`、关联容器和算法被推迟——它们需要更复杂的有效性检查,不适合此提案的范围。
## 具体示例
以下是一些库强化能够捕获的、而今天会静默导致 UB 的示例:
```cpp
1 2 3 4 5 6 std::vector v = {1, 2, 3};
int x = v[5]; // 契约违反:5 >= 3
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back(); // 契约违反:!empty() 为 false
```
```cpp
1 2 3 std::string_view sv("hello");
char c = sv[10]; // 契约违反:10 >= 5
sv.remove_prefix(10); // 契约违反:10 > 5
```
```cpp
1 2 std::optional opt;
int x = *opt; // 契约违反:has_value() 为 false
```
```cpp
1 2 std::span sp(data, 3); // 契约违反:长度不匹配
sp.first<10>(); // 契约违反:10 > size()
```
这些都不是难以想象会发生的情况。它们正是那种在代码审查中漏网、只有在生产环境中才会浮现的 bug。
## 如何激活?
该提案*不*规定激活机制——这有意留给实现决定。实践中,你可能会看到类似以下形式:
- 编译器标志:`-fhardened` 或 `-D_LIBCPP_HARDENING_MODE=...`
- 或者实现特定的构建系统选项
一个有意为之的设计选择:在强化实现中,你不能用 `ignore` 语义覆盖强化的前置条件检查。强化的全部意义在于提供一个坚实的安全基线。如果你可以随意关闭个别检查,那么这种保证就毫无意义。此外,由于规定了强化的前置条件违反是一种契约违反,“我们已经越过了 `ignore` 可以绕过检查的节点”。要检查你的编译器是否已经支持强化,可以使用此处列出的各种特性测试宏 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html#feature-test-macros)。在撰写本文时,GCC 15 和 MSVC 19.44 已经部分实现了 P3471R4 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html)。
## 结论
库强化是那种让你疑惑为什么没有更早标准化的特性之一。它能以极小的代价捕获真实世界的 bug,而且你无需修改现有代码。三大主流实现多年来一直在做它们自己的版本——C++26 终于使其可移植且一致。如果你在意 C++ 代码中的内存安全性(在 2026 年,你应该在意),那么强化的标准库模式应该成为你构建配置的一部分。
## 深入链接
如果你喜欢这篇文章,请
- 点击点赞按钮,
- 订阅我的通讯 (https://sandor-dargo.kit.com/e19f29b0a1)
- 并让我们在 Twitter (https://twitter.com/SandorDargo) 上保持联系!
相似文章
C++26:减少未定义行为
C++26 引入了减少未定义行为的更改,特别是使删除指向不完整类型的指针成为格式错误,从而提高了程序安全性。
C++ 标准库在过去十五年间一直在自我撤步,证据公开
一份详细的目录,列出了从 C++11 到 C++26 期间被正式弃用、非正式不推荐或由于 ABI 约束实际上已损坏但无法修复的 C++ 标准库特性。文章指出,C++ 委员会推出一系列替代品来替换其自身特性的模式始终如一,其中包含一个基准测试,显示 Rust 和 C++ 标准库容器之间的 P99 延迟差异高达 58 倍。
C++26: 平凡的无限循环不再是无定义行为
C++26 将平凡的无限循环标准化为定义明确的行为,消除了之前允许编译器优化掉这些循环的无定义行为,这在嵌入式和内核应用中是有问题的。
C++26 发布了一个无人要求的 SIMD 库
文章批评了 C++26 中的新 std::simd 库,认为它比标量循环慢,编译速度慢,并且被自动向量化器和 Google Highway 等替代库超越,质疑其在经过十年标准化过程后的价值。
C++26:新的调试头文件
C++26 引入了新的 <debugging> 头文件,提供 std::breakpoint()、std::is_debugger_present() 和 std::breakpoint_if_debugging(),通过提案 P2546R5 和 P2810R4 实现标准化,以取代平台特定的调试宏。