ポインタとは
変数はメモリ上の箱でしたね。その箱には住所(アドレス)がついています。ポインタとは、そのアドレスを記憶する変数のことです。
メモリは「番地つきの連続した倉庫」
コンピュータのメモリ(RAM)は、1バイト(8ビット)ごとに通し番号(アドレス)がふられた、ずらっと並んだ棚のようなものです。変数を宣言すると、OSが空いている場所を確保し、そこに番地が決まります。
💾 メモリ (RAM) ― 各マスは1バイト
int n = 10; (4バイト)
int m = 20; (4バイト)
char c = 'A'; (1バイト)
未使用
ポイント:
n の先頭アドレス = 0x1000 /
m の先頭アドレス = 0x1004 /
c のアドレス = 0x1008
int は 4バイト使うので、次の変数は 4番地先に置かれます。このように型の大きさ分だけ連続して確保されます。
家と住所のイメージ: 変数が「家」、アドレスはその「住所(番地)」。ポインタは、その住所を書きとめた「メモ」です。住所さえ知っていれば、その家の中身を参照したり書き換えたりできます。
& 演算子でアドレスを取り出せる: &n と書くと、変数 n のアドレス(例:0x1000)が得られます。printf("%p\n", &n); で実際に住所を表示することも可能。
普通の変数 vs ポインタ
普通の変数
int n = 10;
箱の中に値(10)を直接入れる
ポインタ変数
int *p = &n;
箱の中にnのアドレスを入れる
宣言は 型 *変数名; の形。型はポインタが指す先の型です。int *p は「int型の変数を指すポインタ p」。
&演算子 と *演算子
ポインタを扱うには2つの演算子が必須です。
& (アドレス演算子)
&n → nのアドレス
変数の住所を取得する演算子。
scanfで使う & と同じ!
* (間接参照演算子)
*p → pが指す変数の中身
ポインタの指し示す先の値にアクセス。
読み書き両方できる。
int n = 10;
int *p = &n; // pにnのアドレスを代入
printf("%d", *p); // → 10 (pが指す値)
*p = 99; // nを書き換える!
printf("%d", n); // → 99
アドレスと値 ― ビジュアライザ
ボタンで操作して、変数 n と ポインタ p の関係を確認しましょう。
メモリ内の動きをステップ実行
ポインタがメモリの中でどう動くかを、1行ずつ実行して観察しましょう。ポインタ p は n を指したり m を指したり切り替わります。
プログラム
int n = 10;
int m = 20;
int *p;
p = &n;
*p = 99;
p = &m;
*p = 77;
💾 メモリ (RAM)
「次の行を実行」を押してください...
観察ポイント:
- p = &n; と p = &m; で、矢印(p の指す先)が切り替わる
- *p = 値; は、矢印の先にある変数の中身を書き換える
- p 自身の中身はアドレス(0x1000 や 0x1004)、* を付けて初めて中身にアクセスできる
scanfの & の正体
これまで scanf では scanf("%d", &a); と書いていましたね。この「&」こそがアドレスを渡す演算子だったのです。
なぜ & が必要?
scanf関数は「どこに値を書き込めばいいか」を知る必要があります。そのため変数の住所(アドレス)を渡します。
→ scanfは内部でポインタを使って、その住所の場所に入力値を書き込んでいるのです。
int a;
scanf("%d", &a); // aのアドレスを渡す
// scanf内部では…こんなイメージ
// void scanf(char *fmt, int *p){ *p = 入力値; }
⚠️ よくあるミス: scanf("%d", a); のように & を忘れると、aの中身(不定値)をアドレスとして扱ってしまい、予期せぬ場所に書き込んでプログラムが落ちることがあります。
swap関数 ― ポインタの実用例
2つの変数の値を入れ替える swap関数は、ポインタの代表的な応用例です。
値渡しでは入れ替えできない!
void swap(int a, int b){ ... } のように書くと、呼び出し元の変数は変わりません。
(関数内でコピーを入れ替えているだけだから)
// ポインタを受け取って中身を入れ替える
void swap(int *x, int *y){
int t = *x;
*x = *y;
*y = t;
}
int main(void){
int a = 5, b = 10;
swap(&a, &b); // アドレスを渡す
printf("%d %d", a, b); // → 10 5
}
ポイント:関数にアドレスを渡す=参照渡し。配列を関数に渡すと自動的に参照渡しになっていたのも、これと同じ仕組みです。
ポインタ演算(ポインタの加減算)
ポインタに整数を足したり引いたりすると、型のサイズ分だけアドレスが移動します。配列とポインタの関係を理解する鍵です。
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // arr[0] を指す
printf("%d\n", *p); // 10 (arr[0])
printf("%d\n", *(p + 1)); // 20 (arr[1])
printf("%d\n", *(p + 3)); // 40 (arr[3])
p += 2; // p は arr[2] を指すようになる
printf("%d\n", *p); // 30
p + 1 は「次の要素」であり、「次の1バイト」ではありません。
int型(4バイト)のポインタなら、p + 1 はアドレスが 4バイト 進みます。
配列とポインタの等価性
C言語では、以下はすべて同じ意味です。
| 配列記法 | ポインタ記法 | 意味 |
arr[0] | *arr | 先頭要素 |
arr[i] | *(arr + i) | i番目の要素 |
&arr[i] | arr + i | i番目のアドレス |
ポインタの引き算
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];
int *p2 = &arr[4];
printf("%ld\n", p2 - p1); // 3(要素数の差)
ポインタ同士の引き算は「間にある要素の数」を返します。バイト数ではありません。
自分で書いてみよう ― ポインタ
ポインタを使って値を書き換えるプログラムです。実行してみましょう。
*p = 99; で n の値が書き換わることを確認してください。これが「ポインタ経由で間接的に値を変更する」ということです。
よくある質問(FAQ)
Q. なぜポインタを使うの? 普通の変数では だめ?
A. ポインタが必要な理由は:1. 関数で変数の値を変更したい時(参照渡し)、2. 動的にメモリを確保する時、3. 複雑なデータ構造(リスト、ツリーなど)を作る時、4. 文字列を扱う時などです。ポインタがないと実装不可な機能が多くあります。
Q. * と & の違いが混乱します。どっちどっち?
A. &(アドレス演算子)は「変数がどこに置かれているか」を示すアドレスを取得します。*(間接参照演算子)は「このアドレスに何が入っているか」を見に行きます。ポインタの宣言 `int *p;` の * は「pはint型へのポインタ」という意味です。
Q. NULLポインタって何ですか?
A. NULLポインタは「どこも指していないポインタ」を表し、値は0です。初期化されていないポインタはダングリングポインタと呼ばれ危険です。安全なコードを書くために、ポインタを宣言したら必ず `int *p = NULL;` のように初期化する習慣をつけましょう。
Q. ポインタがポインタを指すってどういうこと?
A. ポインタも変数なので、ポインタ変数自体がメモリに置かれています。したがって「ポインタへのポインタ」も作れます: `int **pp = &p;` のように宣言します。多次元ポインタは複雑ですが、同じ原理を繰り返し適用するだけです。ただし実務ではまれに使うのみです。
確認クイズ
この講座の理解度をチェックしましょう!
Q1. int *p; の p に格納されるのは?
整数値
整数型変数のアドレス
文字列
int *p はint型へのポインタで、int型変数のメモリアドレスを格納します。
Q2. int x=10; int *p=&x; のとき *p の値は?
x のアドレス
10
ポインタ p のアドレス
*p はポインタの参照先の値(間接参照)で、p が x を指しているので *p = 10 です。
Q3. NULL ポインタを参照(*p)するとどうなる?
0が返る
実行時エラー(セグフォ)
コンパイルエラー
NULLポインタの参照はセグメンテーションフォルト等の実行時エラーを引き起こします。使用前のNULLチェックが重要です。
この講座の理解を深めるおすすめ書籍
サイトで動きを理解し、書籍で演習量を補うと効果的です
📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。丁寧な解説で基礎を固められます。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で、演習問題も充実。大学の教科書としても採用多数。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。基礎を終えた後のステップアップに最適。
Amazonで見る
※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。