ポインタは、変数の「場所(アドレス)」を扱う道具です。
ここでは、参照(間接参照)と間接代入を中心に、&
演算子と*
演算子の基本を、初心者の方にも分かりやすく丁寧に解説します。
配列や関数への応用は別記事で扱うため、本記事では単独の変数をポインタで安全に読み書きする方法に集中します。
ポインタの参照と間接代入の基本
ポインタは変数のアドレスを持つ
用語の整理
ポインタは「ある変数がメモリ上のどこにあるのか」という位置情報(アドレス)を保持する特別な変数です。
たとえばint
型の変数x
のアドレスを保持するにはint *p
と宣言します。
このp
の中身は数値データそのものではなくアドレスです。
なぜアドレスを持つと便利か
アドレスを持つことで、別名経由で同じ変数にアクセスできます。
これにより、関数に「場所」を渡して関数内から元の変数を変更したり、データ構造の要素を柔軟に扱ったりできます。
ここで使う操作が参照(間接参照)と間接代入です。
値とアドレスの違い
「値」と「アドレス」は別物です。
値は中身、アドレスは場所です。
次の表で表現の違いを整理します。
表現 | 説明 | 型の例 |
---|---|---|
x | 変数そのものの値 | int |
&x | 変数x のアドレス | int * |
p | ポインタ変数の中身(ある場所のアドレス) | int * |
*p | p が指す先にある値(参照) | int |
例で確認する(値とアドレスの出力)
アドレスはprintf
で%p
を使って出力します。
必ず(void *)
にキャストするのが慣例です。
#include <stdio.h>
int main(void) {
int x = 42; // 値(中身)
printf("x の値: %d\n", x); // 値を表示
printf("x のアドレス: %p\n", (void*)&x); // アドレスを表示
return 0;
}
実行結果(アドレスは環境により異なります)の例:
x の値: 42
x のアドレス: 0x7ffee8b2c9ac
参照の考え方
を先頭につける操作を参照(間接参照)と呼び、「アドレスを通じて本体の値を読む(あるいは書く)」ことを意味します。
たとえばint p = &x;
としておけば、p
はx
と同じ実体の値になります。
よってp
を変更すればx
も変わります。
&演算子でアドレス取得
ポインタの宣言と初期化
型とポインタの関係
ポインタは「何型の値がある場所か」を型として持ちます。
ポインタの型は指す先の型に一致させます。
// int型の値がある場所を指すポインタ
int *pi;
// double型の値がある場所を指すポインタ
double *pd;
// 文字(char)がある場所を指すポインタ
char *pc;
初期化の基本形
&
演算子で変数のアドレスを取得し、そのアドレスをポインタへ代入します。
int x = 10;
int *p = &x; // &x が x のアドレス。p は x を指す
変数のアドレスを代入する
アドレスとポインタの値を確かめる
ポインタ自身の値(中身)はアドレスです。
p
と&x
は一致します。
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x; // x のアドレスを p に保存
printf("x のアドレス (&x): %p\n", (void*)&x);
printf("p の中身 (p): %p\n", (void*)p);
return 0;
}
実行結果(アドレスは環境により異なります)の例:
x のアドレス (&x): 0x7ffee8b2c99c
p の中身 (p): 0x7ffee8b2c99c
よくある誤り
int *p = x;
では、値をアドレスとして解釈しようとしてしまい危険です。
必ず&
でアドレスを代入してください。
*演算子で間接代入
*pで値を読み取る
*p
はp
が指す先にある値を読み取ります。
代入の右辺で使えば値の取得です。
int x = 10;
int *p = &x;
int y = *p; // y に x の値(10)が入る
// ここで y == 10, x == 10
*p = 値で書き込む
間接代入とは、*p = 値;
の形でアドレスの先にある変数へ書き込むことです。
int x = 10;
int *p = &x;
*p = 99; // x に 99 を代入したのと同じ効果
// ここで x == 99
サンプルコードの流れ
ステップ解説
- 変数
x
を用意して値を入れます。 p
にx
のアドレスを入れます(p = &x
)。*p
で読み取って別の変数y
にコピーします。*p = 値
でx
へ新しい値を書き込みます。- すべての値とアドレスを表示して、変化を確認します。
まとめて動かすサンプル
#include <stdio.h>
int main(void) {
int x = 10; // 元の変数
int *p = &x; // x のアドレスを指すポインタ
int y = 0; // 参照で読み取った値を入れる箱
// 1) 現在の状態を表示
printf("[初期] x=%d, &x=%p, p=%p\n", x, (void*)&x, (void*)p);
// 2) 参照: *p で x の値を読み取って y にコピー
y = *p; // *p は x の値を意味する
printf("[参照] y=*p -> y=%d (x=%d)\n", y, x);
// 3) 間接代入: *p に新しい値を書き込むと x が更新される
*p = 99;
printf("[書込] *p=99 -> x=%d (y=%d)\n", x, y);
// 4) さらに x を直接変更すると *p の読み取り結果も変わる
x = 123;
printf("[直接] x=123 -> *p=%d (p は同じアドレス=%p)\n", *p, (void*)p);
return 0;
}
[初期] x=10, &x=0x7ffee8b2c98c, p=0x7ffee8b2c98c
[参照] y=*p -> y=10 (x=10)
[書込] *p=99 -> x=99 (y=10)
[直接] x=123 -> *p=123 (p は同じアドレス=0x7ffee8b2c98c)
この結果から、*p と x が常に同じ実体の値を見ていること、そして間接代入がx
に直接代入するのと同じ効果を持つことが分かります。
安全に使うための注意点
未初期化ポインタを使わない
悪い例
未初期化のポインタを参照(間接参照)すると未定義動作になります。
int *p; // 初期化していない(どこを指すか不明)
*p = 10; // 危険: 不正な場所へ書き込む可能性
良い例
使う前に必ず有効なアドレスを代入するか、まだ決まっていないならNULL
で初期化し、参照前にチェックします。
#include <stdio.h>
#include <stddef.h> // NULL を使うため
int main(void) {
int x = 0;
int *p = NULL; // まだ指す先がない
// ... 条件が整ったら
p = &x; // ここで初めて有効なアドレスを設定
*p = 10; // 安全: x に 10 が入る
printf("x=%d\n", x);
return 0;
}
型を一致させる
型不一致の危険性
指す先の型とポインタの型が一致しないと、読み書きのサイズや解釈が狂い、バグやクラッシュの原因になります。
double d = 3.14;
int *pi = (int*)&d; // 無理な流用(キャストで抑え込んでも危険)
*pi = 100; // double の領域を int として破壊する可能性
正しい宣言
「何を指すか」に合わせてポインタ型を宣言します。
double d = 3.14;
double *pd = &d; // OK: 指す先の型(double)と一致
*pd = 2.71828; // 正しく書き込める
有効なアドレスだけを参照する
スコープ外・寿命切れのアドレスは参照しない
ローカル変数のアドレスを関数の外へ返し、後で参照するのは寿命切れのアドレスを触るため厳禁です。
int *bad(void) {
int local = 123;
return &local; // 危険: 関数終了で local は消える
}
// 受け取って *p すると未定義動作
配列境界外や無効領域は参照しない
範囲外アドレスや解放済みメモリ(動的確保の場合)を参照しないでください。
本記事では動的確保は扱いませんが、いずれの場合も「このアドレスは今もその値の正当な場所か」を常に意識することが重要です。
まとめ
ポインタは値そのものではなく「場所(アドレス)」を扱う変数です。
&
でアドレスを取得し、で参照(読み取り)や間接代入(書き込み)を行います。
サンプルから分かる通り、p と元の変数は同じ実体を見ているため、一方を変えればもう一方も変わります。
安全に使うためには、未初期化ポインタを使わないこと、指す先の型を一致させること、有効なアドレスだけを参照することが大切です。
これらの基礎を押さえれば、関数へのアドレス渡しや配列との連携など、より実践的な活用へスムーズに進めます。