17611538698
info@21cto.com

C++26的强化标准库模式

编程语言 0 11 16小时前

C++ 中的未定义行为 (UB) 是开发人员最难处理的 bug 类型之一。它可能会悄无声息地破坏内存,导致远离实际错误点的崩溃,或者最糟糕的是——在你的机器上碰巧运行是正常的。

实际代码库中相当一部分 UB 并非源于语言的特殊特性,而是源于对标准库的基本误用:例如访问vector越界对象、调用front()空容器或解引用空对象optional

C++26 通过P3471R4引入的强化标准库,正是直接解决这个问题。

图片

什么是库加固?


库强化将标准库中某些未定义行为转换为运行时可检测的契约违规。当强化后的前提条件被违反时,运行时会在任何其他可观察到的副作用之前做出反应——可以将其理解为给目前未定义行为的操作添加边界检查。

这并非新概念。三大标准库实现均已提供厂商特定的加固模式。问题在于这些机制各不相同,缺乏可移植性,且规范不一致。P3471R4旨在将现有实现的功能标准化。

标准化的强化版本成为合约的首次应用(在P2900中规定)。这与语言的发展方向完全一致。

动机:现实世界的证据


支持这项功能的最有力论据是谷歌的生产经验。在提案中也引用了这一点:将强化后的 libc++ 应用于“数亿行 C++代码”后,发现了 1000 多个漏洞,其中包括一些安全关键漏洞。平均性能开销低得惊人,仅为0.30% ——不到三分之一个百分点。开销的降低得益于编译器在优化过程中消除冗余检查的能力。

影响不仅限于安全性:团队观察到生产环境中的基线段错误率降低了 30%,这表明代码的正确性得到了全面提高。

这真是个了不起的结果!

什么东西会变强?


该提案特意只关注内存安全的前提条件。检查所有前提条件“并非我们的目标” ——我们的目标是找出那些添加成本低但价值高的检查。

std::span

  • operator[]需要idx < size()
  • front()back():要求非空
  • first(),,last()subspan()验证countoffset是否在范围内
  • 从范围构造函数:验证范围是否匹配


std::string_view

  • operator[]需要pos < size()
  • front()back():要求非空
  • remove_prefix()remove_suffix(): 要求n <= size()


序列容器 vectordeque,,,,,listforward_list array string

  • operator[]:需要n < size()basic_string因为n <= size()访问空终止符是有效的)
  • front()back(): 要求!empty()
  • pop_front()pop_back(): 要求!empty()


std::optionalstd::expected

  • operator->()operator*()optional:需要has_value()
  • 值访问expected:需要has_value()
  • error()开启expected:需要!has_value()


std::mdspan

  • operator[]验证其范围内的所有多维指标
  • 构造函数:在转换过程中验证静态范围是否匹配


std::bitsetstd::valarray

  • operator[]:需要pos < size()/n < size()


有些内容被有意省略。例如,基于迭代器的操作erase()、关联容器和算法都被推迟讨论——它们需要更复杂的有效性检查,这超出了本提案的范围。

具体的例子


以下是一些库加固措施可以发现并解决目前悄无声息地导致未定义行为的例子:

std::vector<int> v = {123};int x = v[5];       // contract violation: 5 >= 3v.pop_back();v.pop_back();v.pop_back();v.pop_back();       // contract violation: !empty() is false
std::string_view sv("hello");char c = sv[10];          // contract violation: 10 >= 5sv.remove_prefix(10);     // contract violation: 10 > 5
std::optional<int> opt;int x = *opt;     // contract violation: has_value() is false
std::span<int, 5> sp(data, 3);  // contract violation: extent mismatchsp.first<10>();                  // contract violation: 10 > size()
这些情况并非难以想象。这类漏洞往往会在代码审查中被忽略,最终在生产环境中暴露出来。

你如何激活它?


该提案并未对激活机制进行标准化——这有意留给具体实现者自行决定。实际环境,我们会看到类似这样的情况:

  • 编译器标志:-fhardened-D_LIBCPP_HARDENING_MODE=...
  • 或者也可以是特定于实现的构建系统选项


一个较刻意的设计选择是:在强化实现中,你无法通过ignore语义覆盖强化的前提条件检查。强化的意义就在于提供一个可靠的安全基线。如果可以随意关闭单个检查,那么这种保证就毫无意义。此外,由于强化的前提条件被明确定义为违反契约,“我们已经超越了忽略检查可以绕过检查的阶段”

要检查你的编译器是否已支持强化,可以使用以下列出的各种特性测试宏

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html#feature-test-macros

截至撰写本文时,GCC 15 和 MSVC 19.44 已经部分实现了P3471R4

结语


库加固是那种让人不禁疑惑为何没有更早标准化的特性之一。它能以极低的成本捕获真正的 bug,而且无需修改任何代码。三大主流实现多年来一直在各自实现这一功能——C++26 最终使其具备了可移植性和一致性。

如果你关心 C++ 代码中的内存安全,到了 2027 年,你应该更加关心它,而强化标准库模式应该是你的构建配置的重要一部分。

作者:场长

评论

我要赞赏作者

请扫描二维码,使用微信支付哦。

分享到微信