このページはC++29に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
C++26までの規格では、整数からポインタへの変換によって、どのオブジェクトを指す有効なポインタが得られるのか(ポインタの由来、provenance)が厳密には規定されていなかった。C++29では、C++の抽象マシンがもともと持つ「オブジェクトのアドレスは実行ごとに定まらない(非決定的である)」という性質を土台として、この動作が次のように規定される。
- 整数からポインタへの変換では、その整数へ変換できるポインタ値が1つ以上存在する場合、そのうちプログラムの動作が定義されることになる値の中から、未規定に選択された1つが結果となる。そのような値が存在しない場合、動作は未定義である
- この規則によって、実際に取得したことのあるアドレスを、演算によって確実に再現する操作は定義された動作となる。一方で、取得していないアドレスを推測してポインタを作る操作は、アドレスがたまたま一致する実行がありうるとしても、未定義動作である
int x = 0;
int y = 0;
std::uintptr_t p = (std::uintptr_t)&x;
std::uintptr_t q = (std::uintptr_t)&y;
// XOR演算による値の交換。アドレスがどのように選ばれた実行であっても、
// 最終的にqはxのアドレス、pはyのアドレスを確実に再現するため、
// 変換結果はそれぞれx、yを指す有効なポインタとなる
p ^= q;
q ^= p;
p ^= q;
*(int*)q = 1; // OK: xへの書き込み
*(int*)p = 2; // OK: yへの書き込み
*(int*)8675309 = 3; // 未定義動作: 取得したことのないアドレスの推測
この規則は、std::memcpy()やstd::bit_cast()などのビットコピーによってポインタの値表現を復元した場合にも同様に適用される。
また、変換と並行して確保された記憶域を指すポインタ値が選択されることもありうる。これによって、指していた記憶域が解放され、別スレッドが同じアドレスの記憶域を確保し直すようなロックフリーアルゴリズム(いわゆるpointer zap問題の典型例)を、整数への往復変換を用いて定義された動作として書けるようになる。
仕様
- 整数型・列挙型からポインタ型への明示的な変換は、以下のように規定される
- トリビアルコピー可能型について、次の規定が整備される(このページに関係する範囲の要約)
- ある評価がポインタ値を生成し、その評価が、指される領域の記憶域の生存期間の開始よりも前に発生する(happens before)場合、動作は未定義である
- 変換と並行して確保された記憶域を指すポインタ値は選択されうるが、その評価より確実に後に確保される記憶域を指すポインタは作れない
- 任意の2つのポインタ値の等値比較が、一貫した結果を返すことが規定される
例
#include <cstdint>
#include <iostream>
int main()
{
int x = 0;
int y = 0;
// ポインタを整数へ変換し、XOR演算によって2つのアドレスを入れ替える。
// どのようなアドレスが選ばれた実行であっても、qはxのアドレス、pはyの
// アドレスを確実に再現するため、この操作の動作は定義される
std::uintptr_t p = (std::uintptr_t)&x;
std::uintptr_t q = (std::uintptr_t)&y;
p ^= q;
q ^= p;
p ^= q;
*(int*)q = 1; // xへの書き込み
*(int*)p = 2; // yへの書き込み
std::cout << x << y << std::endl;
}
出力
12
この機能が必要になった背景・経緯
ポインタと整数の相互変換の意味論としては、C標準の検討で提案されたPNVI-ae-udiモデル(記憶域の「露出 (exposure)」に基づくモデル)などが知られていた。しかし露出に基づくモデルには、std::memcpy()の代わりに明示的なコピーループを書くだけで記憶域が露出して最適化が阻害される、データ依存のない操作の順序に結果が依存するなど、恣意的に見える点があった。
本提案は、C++の抽象マシンでは「アドレスの選択は実行ごとに定まらない」ことを利用して、露出という概念を導入せずに同等の意味論を実現した。整数からポインタへの変換で「動作が定義される値が選択される」という規定は、暗黙的なオブジェクト生成がオブジェクトを選択する方式と同じ(都合のよい非決定性、angelic nondeterminism)である。
あわせて、記憶域が解放されて同じアドレスに確保し直されることでポインタが無効化される「pointer zap」問題のうち、整数への往復変換を使うロックフリーアルゴリズムのユースケースが、定義された動作として書けるようになった。
関連項目
- C++20 暗黙的なオブジェクト生成
- C++29 アトミック操作とvolatileアクセスでの無効なポインタ値の扱いを規定
- C++29 無効なポインタ値の読み書き・コピーを定義された動作とする
std::uintptr_tstd::bit_cast()
参照
- P2434R5 Nondeterministic pointer provenance
- P2318R1 A Provenance-aware Memory Object Model for C
- C標準向けに提案された、露出に基づくポインタ由来のモデル (PNVI-ae-udi) の分析