C++におけるconst修飾子の三層構造: コンパイラから物理メモリまで

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変数の実際の不変性保証レベルを決定する根本要因である。

タグ: C++ memory-protection undefined-behavior rodata Pointer-Arithmetic

7月30日 22:57 投稿