C++のconst修飾子は変数の不変性を保証するが、その実装は3つの抽象化レイヤーで構成される。これらのレイヤーは相互に作用しつつ、データの不変性を段階的に強化する仕組みとなっている。
コンパイラレベルの制約
コンパイラはconst修飾された変数への直接的な代入を構文チェックで検出する。次の例ではコンパイルエラーが発生する:
constexpr int initialValue = 100;
void modifyDirectly() {
initialValue = 200; // コンパイル時エラー
}
この段階では、const修飾子は単なる構文規則として機能する。
OSレベルのメモリ保護
ポインタ操作でコンパイラチェックを回避しても、OSがページ単位のメモリ保護を実施する。グローバル領域のconst変数を書き換えると段錯誤が発生する:
const double globalConstant = 3.14;
void bypassCompilerCheck() {
double* mutablePtr = const_cast<double*>(&globalConstant);
*mutablePtr = 6.28; // 実行時エラー(SIGSEGV)
}
OSは.rodataセクションを含むメモリページに読み取り専用属性を設定するため、ハードウェアレベルで書き込みをブロックする。
物理メモリの制限
スタック領域のconst変数は異なる挙動を示す。次の実験コードでは、一時的に値の上書きが可能である:
int validateStackBehavior() {
volatile const int stackValue = 42;
printf("Original: %d\n", stackValue);
int* hackPtr = const_cast<int*>(&stackValue);
*hackPtr = 99;
printf("Modified: %d\n", stackValue); // 実際の出力は未定義
return stackValue;
}
この現象が起こるのは、スタック領域が物理ROMではなくRAMにマップされるため。OSはページ属性を設定するが、物理デバイス自体が書き込みを禁止していない。ただし、この操作は未定義動作であり、実行環境によっては即座に異常終了する。
メモリ領域の比較
ヒープ領域での実験結果から、メモリ配置が保護メカニズムに与える影響が明確になる:
void heapExperiment() {
const char* heapData = new const char('A');
char* writable = const_cast<char*>(heapData);
*writable = 'Z'; // ヒープ領域では実行可能(環境依存)
printf("Heap value: %c\n", *heapData);
delete heapData;
}
グローバル変数が配置される.rodataセクションは物理ROM相当の保護を受けるが、スタック/ヒープ領域はページ属性のみで保護される。この違いが、const変数の実際の不変性保証レベルを決定する根本要因である。