広告スペース

第25回 ポインタの基礎

C言語のポインタをメモリ可視化で理解。アドレスと間接参照を図解。

ポインタとは

変数はメモリ上の箱でしたね。その箱には住所(アドレス)がついています。ポインタとは、そのアドレスを記憶する変数のことです。

メモリは「番地つきの連続した倉庫」

コンピュータのメモリ(RAM)は、1バイト(8ビット)ごとに通し番号(アドレス)がふられた、ずらっと並んだ棚のようなものです。変数を宣言すると、OSが空いている場所を確保し、そこに番地が決まります。
💾 メモリ (RAM) ― 各マスは1バイト
int n = 10; (4バイト)
int m = 20; (4バイト)
char c = 'A'; (1バイト)
未使用
ポイント: n の先頭アドレス = 0x1000m の先頭アドレス = 0x1004c のアドレス = 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 の関係を確認しましょう。
アドレス 0x1000
int n
アドレス 0x2000
int *p
ボタンを順に押してください...

メモリ内の動きをステップ実行

ポインタがメモリの中でどう動くかを、1行ずつ実行して観察しましょう。ポインタ pn を指したり m を指したり切り替わります。
プログラム
int n = 10;
int m = 20;
int *p;
p = &n;
*p = 99;
p = &m;
*p = 77;
💾 メモリ (RAM)
0x1000
int n
0x1004
int m
0x2000
int *p
「次の行を実行」を押してください...
観察ポイント:

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バイト 進みます。
10
p+0
20
p+1
30
p+2
40
p+3
50
p+4

配列とポインタの等価性

C言語では、以下はすべて同じ意味です。
配列記法ポインタ記法意味
arr[0]*arr先頭要素
arr[i]*(arr + i)i番目の要素
&arr[i]arr + ii番目のアドレス

ポインタの引き算

int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];
int *p2 = &arr[4];
printf("%ld\n", p2 - p1);  // 3(要素数の差)
ポインタ同士の引き算は「間にある要素の数」を返します。バイト数ではありません。

自分で書いてみよう ― ポインタ

ポインタを使って値を書き換えるプログラムです。実行してみましょう。
pointer.c
出力
「実行」を押してください...
*p = 99;n の値が書き換わることを確認してください。これが「ポインタ経由で間接的に値を変更する」ということです。
広告スペース

関連する講座

関数編
第24回 配列を引数に
C言語で関数に配列を渡す方法。ポインタとの関係も解説。
発展編
第28回 動的メモリ(malloc/free)
C言語の動的メモリ確保。malloc, free, メモリリークの原因と対策。
発展編
第26回 構造体(struct)
C言語のstruct(構造体)の定義・メンバアクセス・配列との組合せを解説。
← 前の講座
第24回 配列を引数に
次の講座 →
第26回 構造体(struct)

よくある質問(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チェックが重要です。

この記事をシェア
X(Twitter)でシェア Facebookでシェア LINEで送る はてブ

この講座の理解を深めるおすすめ書籍

サイトで動きを理解し、書籍で演習量を補うと効果的です

📘
苦しんで覚えるC言語
MMGames 著
初心者向けの定番入門書。丁寧な解説で基礎を固められます。
Amazonで見る
📗
新・明解C言語 入門編
柴田望洋 著
図解が豊富で、演習問題も充実。大学の教科書としても採用多数。
Amazonで見る
📙
プログラミング言語C 第2版
B.W.カーニハン, D.M.リッチー 著
通称K&R。C言語の原典。基礎を終えた後のステップアップに最適。
Amazonで見る

※ 上記リンクはアフィリエイトリンクです。購入によりサイト運営を支援いただけます。