C言語のconstは、オブジェクトの変更可能性を制限することを可能にします。関数のパラメータを扱う際には、データを偶発的な変更から保護するのに役立ちます。宣言の主な違いは、const修飾子がどこに関連するか、ポインタに対してどこに位置するかによります。
さまざまな宣言の例:
void func(const int *ptr); // 定数intへのポインタ void func(int * const ptr); // intへの定数ポインタ void func(const int * const ptr); // 定数intへの定数ポインタ
const int *ptr — データは変更不可ですが、ポインタ自体は再代入可能です。int *const ptr — データは変更可能ですが、ポインタ自体は再代入不可です。const int *const ptr — データもポインタも関数内で変更できません。**constの正しい使用:**は、以下を可能にします。
void print_array(const int *arr, size_t n) { for (size_t i = 0; i < n; ++i) { printf("%d ", arr[i]); // arr[i] = 10; // エラー: constデータを変更しようとしました } }
質問: 定数変数のアドレスを通常のポインタに代入できますか?
期待された間違った回答: "はい、ポインタの宣言からconstを外せば、コンパイラは許可します。"
正しい回答: 「constの降格」は明示的な型キャストを伴う場合のみ許容されますが、これはconstとして宣言されたオブジェクトを変更しようとした場合に未定義の動作を引き起こします。これは行うべきではない — constのセマンティクスを破り、実行時エラーを引き起こします。
例:
const int x = 5; int *ptr = (int*)&x; *ptr = 10; // UB: constオブジェクトの変更
ストーリー
大規模なプロジェクトで、プログラマーがconstポインタを通常のポインタにキャストし、読み取り専用メモリセグメントのデータを変更しようとしました。一部のプラットフォームではプログラムが異常終了(セグメンテーションフォルト)し、他のプラットフォームでは気づかないバグが発生し、デバッグが難しくなりました。
ストーリー
配列を操作するライブラリで、開発者がパラメータをconstとして宣言するのを忘れました。その結果、不適切な関数呼び出しが元のデータを意図せず変更し、配列の状態が同期されず、後の処理ブロックで深刻なバグを引き起こしました。
ストーリー
他のライブラリに渡されるコールバック関数を書く際に、入力バッファのためにconstを指定するのを忘れました。その結果、ライブラリは定数文字列のデータを変更しようとしたため、一部のOSでクラッシュを引き起こし、問題のソースについて長い討議が必要になりました。