HN 日本語サマリー

← 一覧へ戻る
プログラミング

C++26: 標準ライブラリのハードニング実験

C++26: Standard Library Hardening Experiments (cppstories.com)

75 pointsby ibobev39 コメント

要約

C++26では、標準ライブラリの実装に「ハードニング」という概念が導入されました。これは、配列の境界外アクセスなどの未定義動作につながる可能性のあるプログラミングエラーを、実行時に検出し、プログラムを安全に終了させるための仕組みです。コンパイラやライブラリベンダーは、このハードニングモードを有効にするための独自のコンパイラフラグやマクロを提供しています。

全文翻訳

最終更新: 2026年8月30日 C++26: 標準ライブラリのハードニング実験 目次 "ハードニング" は2026年のC++界隈で非常に人気のある言葉のようです。この記事では、この言葉の意味を探り、いくつかのコアな例を見ていきます。ハードニングされたライブラリはC++を完全に安全にできるのでしょうか?見てみましょう。 コアアイデア std::vectorについて学んだとき、i番目の位置の要素に少なくとも2つの式でアクセスできることを覚えているかもしれません: std::vector<int> v { 1, 2, 3, 4 }; v[i] = 10; // あるiについて v.at(j) = 11; // あるjについて この2つの主な違いは、[] はチェックなし(存在しない要素にアクセスしようとすると未定義の動作を生成する可能性がある)であるのに対し、.at() は std::out_of_range をスローする可能性がある(したがって、定義済みの動作である)ということです。 C++26の変更点 C++26では、標準はハードニングされた実装という概念を導入します。標準ライブラリ実装がハードニングされているかどうか、そしてそのモードがどのように有効化されるかは、実装定義です。 std::vector<T, Allocator>::operator[](size_type pos) について: C++標準 条件 (C++26まで) pos < size() が偽の場合、動作は未定義です。 C++26以降 pos < size() が偽の場合: 実装がハードニングされている場合、契約違反が発生します。 実装がハードニングされていない場合、動作は未定義です。 言い換えれば、この "ハードニング" モードに切り替えると、単なる未定義の動作ではなく、定義済みのエラー/違反が得られることになります。 言葉遣いとよくある質問を解きほぐしましょう: .at() では例外が発生する可能性があります…では、なぜ新しい代替手段が必要なのでしょうか?それはもっともな質問です。要するに、at() と [] は異なるインターフェースとパフォーマンス/エラー処理アプローチを持っています。さらに重要なのは、例外を簡単にオフにできないことです(オフにすることはできますが、std::terminateが呼び出されるだけで、あまり柔軟ではありません)。したがって、ハードニングは operator[] を at() に変更するのではなく、プログラミングエラーを検出し、メモリ安全でない未定義の動作を許容する代わりに終了させます。 "契約違反が発生する" - これがここでの鍵となります。"ハードニング" 機能は、C++26にも採択された契約 (Contracts) を使用して表現されます。C++26は、新しい契約モデルを使用してハードニングされた前提条件を指定します。ハードニングされた実装でそれを違反すると、終了セマンティクスで評価される契約違反が発生します。ただし、ライブラリ実装がこれらのチェックを実際の pre, post, または contract_assert 言語構文を使用して実装する必要はありません。 では、この契約違反とは何でしょうか?通常のC++26契約は、ignore, observe, enforce, または quick-enforce セマンティクスを使用できます。ハードニングされた標準ライブラリの前提条件はより制限的です。ハードニングされた実装では、終了セマンティクスを使用する必要があるため、失敗したチェックの後に実行を継続することはできません。これらのモードを切り替える方法は実装依存です。詳細はこちらをご覧ください:契約アサーション (C++26以降) - cppreference.com 実行時に機能しますか? はい、実際には定数式で実行できますが、さらに重要なのは、実行時に実行されることです。 これは、GLIBCXX_ASSERTIONS や _ITERATOR_DEBUG_LEVEL などとどのように関係しますか? C++26は、これらのベンダー固有のチェッカーを持ち込み、共通の、定義済みのルールセットを作成しようとしています。 主な質問:これを有効にするにはどうすればよいですか? GCC / libstdc++: _GLIBCXX_ASSERTIONS は、軽量な標準ライブラリ前提条件チェックを有効にします。GCCのより広範な -fhardened オプションは、他のセキュリティオプションとともにこれを自動的に有効にします。 Clang / libc++: _LIBCPP_HARDENING_MODE を NONE, FAST, EXTENSIVE, および DEBUG モードで使用します。 MSVC STL: _MSVC_STL_HARDENING=1 は、ハードニングをグローバルに有効にします。個々の型は、_MSVC_STL_HARDENING_VECTOR や _MSVC_STL_HARDENING_OPTIONAL などのマクロで制御できます。 注: 本稿執筆時点(2026年8月)では、コンパイラおよびライブラリベンダーはまだC++26機能を完成させています。以下のオプションは現在のベンダーのハードニングメカニズムであり、P3471/P3697/P3878のコアドキュメントおよび提案の完全な実装を必ずしも表すものではありません。 コアドキュメントおよび提案 C++26時点では、この機能全体を構成する以下のペーパーがあります: P3471 - メインの標準ライブラリハードニング P3697 - C++26標準ライブラリハードニングへのマイナーな追加 - basic_stacktrace, shared_ptr<T[N]>, view_interface (front, back), counted_iterator, common_iterator P3878 - 標準ライブラリハードニングは終了セマンティクスを使用すべきである。ハードニングされた前提条件違反が単に観測されてから、ハードニングが防止しようとしていたUBに継続することを保証します。 Hardening Modes — libc++ ドキュメント 標準にハードニングを追加するために、この提案はハードニングされた前提条件という概念を導入します。ハードニングされた前提条件とは、ハードニングされた実装で契約違反につながる前提条件です。ライブラリにハードニングを追加することは、主に既存の前提条件の一部を仕様におけるハードニングされた前提条件に変換することから成ります。 ハードニングの実装の候補となる条件は何でしょうか? 前提条件の違反がメモリ安全性問題(境界外アクセスまたは未初期化メモリへのアクセス)を引き起こす場合。 呼び出しサイトには、チェックを実行するために必要なすべてのデータがある場合。 チェックは定数時間で行え、比較的オーバーヘッドが少ない場合。 C++26 ハードニング条件 チェックされる条件/メンバ関数をまとめたものです。 カテゴリ クラス/型 ハードニングされた操作 シーケンスコンテナ array, vector, inplace_vector, deque, list, forward_list operator[], front(), back(), pop_front(), pop_back() コンテナビュー span, mdspan, view_interface construction, operator[], front(), back(), first(), last(), subspan() イテレータアダプタ common_iterator, counted_iterator construction, operator*, operator->, operator[], operator++, arithmetic, comparisons, iter_move, iter_swap 文字列 basic_string, basic_string_view operator[], front(), back(), pop_back(), remove_prefix(), remove_suffix() 汎用ユーティリティ bitset, optional, expected operator[], operator*, operator->, error() スタックトレース basic_stacktrace current(), operator[] スマートポインタ shared_ptr<T[N]> operator[] 数値配列 valarray operator[] cppreference.com には、すべての条件と標準ライブラリ型をまとめたクールな表があります。「ハードニングされた前提条件を持つ関数」をご覧ください https://en.cppreference.com/cpp/standard_library 基本的な例 基本的な "hello world" の例から始めましょう。デフォルトのコンパイラ動作と、ハードニングオプションでどのように変化するかを見てみます。 #include <vector> #include <iostream> int main() { std::vector<int> v { 1, 2, 3 }; int a = 10; std::cin >> a; v[a] = a; std::cout << "hello world!"; } GCC 16.1 で -std=c++26 のみを使用して実行し、100000 を入力として渡した場合: プログラムの戻り値: 139 プログラムの標準エラー出力 /cefs/38/383ad2f84cbd57a52fd68bbe_consolidated/compilers_c++_x86_gcc_16.1.0/include/c++/16.1.0/bits/stl_vector.h:1253: constexpr std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed. プログラムはシグナル: SIGSEGV で終了しました。 うーん…デフォルトで既にハードニングされているのでしょうか? @Compiler Explorer を参照してください。 GCC 16.1 では、最適化なしでコンパイルする場合、ハードニングを明示的に有効にする必要さえありません。現在の libstdc++ は、最適化なしでコンパイルするとデフォルトで _GLIBCXX_ASSERTIONS を有効にします。最適化が有効になると、これらのアサーションはデフォルトで無効になります。 したがって、-O2 でコンパイルすると、次のようになります。 プログラムの戻り値: 139 プログラムの標準エラー出力 プログラムはシグナル: SIGSEGV で終了しました。 こちらを参照してください @Compiler Explorer 言い換えれば、最適化なしでは、デフォルトで実行時チェックが有効になっている可能性があります。 一方、最適化されたGCCビルドでハードニングモードを有効にするには、次を指定する必要があります。 -std=c++26 -O2 -D_GLIBCXX_ASSERTIONS プログラムの戻り値: 139 プログラムの標準エラー出力 /cefs/38/383ad2f84cbd57a52fd68bbe_consolidated/compilers_c++_x86_gcc_16.1.0/include/c++/16.1.0/bits/stl_vector.h:1253: constexpr std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed. プログラムはシグナル: SIGSEGV で終了しました。