C言語のプログラミングを学んでいると、ポインタとconst修飾子の組み合わせに戸惑う場面が多々あります。
特に「const char*」と「char* const」は、見た目が非常に似ているため、どちらが何を保護しているのか混乱しやすい要素です。
これらの違いを正しく理解することは、バグの少ない安全なコードを記述するために不可欠なスキルと言えます。
この記事では、これら二つの宣言が持つ意味の違いを、具体的なコード例と併せて分かりやすく整理していきます。
const修飾子とポインタの基本ルール
C言語におけるconst修飾子は、変数の値を変更できないように制限するために使用されます。
ポインタ変数に対してconstを使用する場合、「ポインタが指し示す先の値」を固定したいのか、それとも「ポインタ自身の保持するアドレス」を固定したいのかによって記述方法が変わります。
これを見分けるための最も簡単な方法は、宣言を右から左に向かって読むことです。
この「右から読み解くルール」を意識するだけで、複雑なポインタ宣言の意味を即座に判断できるようになります。
const char* の意味と使い方
まずは「const char* ptr;」という宣言について詳しく見ていきましょう。
この宣言を右から読むと、「ptrは、charのconst(定数)へのポインタである」という意味になります。
つまり、「ポインタが指し示す先の内容を書き換えることはできない」が、「ポインタが指すアドレス自体を変更することはできる」という状態です。
指し示す内容の変更(禁止)
以下のコードは、ポインタ経由で文字を書き換えようとした場合にコンパイルエラーが発生する例です。
#include <stdio.h>
int main() {
const char* message = "Hello";
// 以下はコンパイルエラーになります
// message[0] = 'h';
printf("%s\n", message);
return 0;
}
ポインタのアドレス変更(許可)
一方で、ポインタが指す場所を別の文字列に変更することは可能です。
#include <stdio.h>
int main() {
const char* message = "Hello";
// ポインタが指すアドレスを書き換えることは可能です
message = "Goodbye";
printf("%s\n", message);
return 0;
}
Goodbye
このように、const char*は関数の引数などで「渡された文字列の内容を破壊したくない」という用途で非常によく使われます。
char* const の意味と使い方
次に「char* const ptr;」という宣言について解説します。
これを右から読むと、「ptrは、charへのポインタのconst(定数)である」という意味になります。
つまり、「ポインタが保持しているアドレス自体を書き換えることはできない」が、「そのアドレスにある内容を書き換えることはできる」という状態を指します。
ポインタのアドレス変更(禁止)
この宣言では、一度アドレスを代入すると、後から別の場所を指すように変更することはできません。
#include <stdio.h>
int main() {
char str1[] = "Hello";
char str2[] = "World";
char* const ptr = str1;
// 以下はポインタ自体を書き換えようとしているためコンパイルエラーになります
// ptr = str2;
printf("%s\n", ptr);
return 0;
}
指し示す内容の変更(許可)
しかし、ポインタが指しているメモリ領域の内容を書き換えることは許可されています。
#include <stdio.h>
int main() {
char str[] = "Hello";
char* const ptr = str;
// 指し示す先の中身を書き換えることは可能です
ptr[0] = 'J';
printf("%s\n", ptr);
return 0;
}
Jello
特定のメモリバッファを固定して運用したいが、バッファ内のデータは更新したいという状況で利用されます。
両者の違いを比較表で整理
理解を深めるために、それぞれの性質を表にまとめました。
| 宣言形式 | 名称 | 内容の変更 ( *ptr = … ) | アドレスの変更 ( ptr = … ) |
|---|---|---|---|
const char* ptr | 定数へのポインタ | 不可 | 可能 |
char* const ptr | ポインタ定数 | 可能 | 不可 |
const char* const ptr | 定数へのポインタ定数 | 不可 | 不可 |
ここで新しく登場した「const char* const ptr」は、中身もアドレスも両方固定したい場合に使用します。
読み取り専用のデータを指し、かつそのポインタ自体も変更させたくないという最も制約の強い宣言です。
実践的な使い分けのポイント
実際の開発現場では、9割近いケースで「const char*」が使用されます。
これは、関数の引数として文字列を渡す際に、読み取り専用であることを明示し、誤ってデータを破壊することを防ぐためです。
一方で「char* const」は、特定のハードウェアレジスタのアドレスを指すポインタなど、特殊なメモリ管理が必要な場面で限定的に使われます。
プログラミングにおいて大切なのは、「変更する必要のないものはすべてconstにする」という原則です。
この原則に従うことで、不意の代入によるバグをコンパイル時点で検出できるようになり、コードの信頼性が飛躍的に向上します。
まとめ
C言語におけるポインタとconstの組み合わせは、一見すると複雑ですが、ルールを覚えれば非常に論理的です。
「const char*」は「指し示す内容が定数」であり、文字列の保護に役立ちます。
「char* const」は「ポインタ変数の値(アドレス)が定数」であり、接続先の固定に役立ちます。
混乱したときは、宣言を右から読み直すという基本に立ち返ってみてください。
この違いを正しく使い分けることで、より堅牢で意図の明確なプログラムを書くことができるようになります。
今回解説した内容が、あなたのC言語学習の一助となれば幸いです。
