
C言語基礎|ポインタと配列
配列名は“先頭要素の住所”として動く。ここを押さえると、配列とポインタが一気に仲良くなる。
配列とポインタは別モノです。……なのに、C言語ではとっても仲が良いです。
その理由は、Cのルールとして
配列名は、配列の先頭要素へのポインタとして解釈される
という強力な規則があるからです。
このルールのおかげで、配列を関数に渡す・ループで走査する・p+i で要素にアクセスする、といった便利ワザが使えるようになります。
ただし、例外もあって、sizeof と & の文脈では「配列名がポインタ扱いにならない」ので、そこも一緒に押さえておくと安心です。
重要:配列名は先頭要素へのポインタとして解釈される
配列 a があるとき、多くの場面で式 a は &a[0] と同じ意味になります。
配列名 a の基本ルール
| 式 | だいたい同じ意味 | 型(要素型が int の場合) |
|---|---|---|
| a | &a[0] | int * |
| a + i | &a[i] | int * |
| *(a + i) | a[i] | int |
この「a+i が &a[i]」という対応が、ポインタ算術の理解の中心です。
配列とポインタは何が違う?(混同しないために)
似ているからこそ、違いも表で整理しておきます。
配列とポインタの違い
| 観点 | 配列 | ポインタ |
|---|---|---|
| 正体 | 複数要素が連続した領域 | 住所を入れる変数 |
| 代入 | 原則できない(a = p は不可) | できる(p = a は可) |
| sizeof | 配列全体のサイズ | ポインタ自体のサイズ |
| 指す先 | 要素が実体として存在 | どこを指すかは値次第 |
「配列名がポインタとして見える」のはあくまで“多くの式の中での解釈”であって、配列そのものがポインタに変身するわけではありません。
ポインタ算術(p+i は i 要素ぶん進む)
ここが配列×ポインタの気持ちよさポイントです。
p が配列要素を指しているとき、p+i は i 個後ろの要素を指す
注意点は、「i バイト後ろ」ではなく i 要素後ろ だということです。
要素サイズ(sizeof(要素型))を掛けたぶん、内部的にはアドレスが進みます。
図:int 配列(要素サイズ4バイトの例)のアドレスの進み方

- p = &a[0] なら p+1 は &a[1]
- p = &a[2] なら p-1 は &a[1]、p+1 は &a[3]
サンプルプログラム
ポインタで配列を走査して合計を出すプログラム例です。
プロジェクト名:chap10-10-1 ソースファイル名:chap10-10-1.c
#include <stdio.h>
int main(void)
{
int score[5] = {10, 20, 30, 40, 50};
puts("配列をポインタで走査して合計を求めます。");
int *p = score; // score は &score[0] と同じ意味になりやすい
int sum = 0;
for (int i = 0; i < 5; i++) {
printf("p + %d が指す値 = %d\n", i, *(p + i));
sum += *(p + i);
}
printf("合計 = %d\n", sum);
return 0;
}さっきのコードを表で読み解く
式の意味(score[5] の場合)
| 式 | 意味 | 例(i=3) |
|---|---|---|
| p | 先頭要素へのポインタ | &score[0] |
| p + i | i個後ろの要素へのポインタ | &score[3] |
| *(p + i) | その要素の値 | score[3] |
この表は「ポインタ算術と配列添字の対応」を、1行ずつ翻訳しているイメージです。
p が先頭を指すとは限らない(途中要素からもOK)
ポインタは「配列全体」を指すのではなく「どれか1要素」を指します。
だから途中要素からスタートすることもできます。
図:p が score[2] を指している場合

例外:配列名が先頭要素へのポインタとみなされない文脈(2つ)
ここ、つまずきやすいので丁寧にいきます。
配列名は原則ポインタっぽく扱われますが、例外が2つあります。
➀ sizeof のオペランドになったとき
sizeof(score) は、ポインタのサイズではなく 配列全体のサイズです。
➁ アドレス演算子 & のオペランドになったとき
&score は、先頭要素へのポインタではなく 配列全体へのポインタになります。
例外の整理(score が int[5] の場合)
| 式 | 何を表す? | 型のイメージ |
|---|---|---|
| score | 先頭要素へのポインタっぽく解釈される | int * |
| sizeof(score) | 配列全体のバイト数 | 例えば 20(intが4バイトなら) |
| &score | 配列全体へのポインタ | int (*)[5] |
score / &score / sizeof(score) の違い
score: &score[0] を指すイメージ(多くの式の中で)
&score: 配列まるごと(5要素の塊)を指すイメージ
sizeof: 配列まるごとの大きさを返す
説明
- score は「先頭要素の住所」として動く場面が多い
- &score は「配列全体の住所」なので、型も別物になる
- sizeof(score) は「配列全部のサイズ」を計算するので、ポインタサイズとは一致しない
登場する命令(関数)と書式・何をする命令なのか
puts
- 書式:puts(文字列);
- 何をする?:文字列を表示して改行も付ける。
printf
- 書式:printf(書式文字列, 引数1, 引数2, ...);
- 何をする?:書式に従って表示する(%d で整数表示など)
sizeof(演算子)
- 書式:sizeof(型) または sizeof(式)
- 何をする?:型や式が占めるバイト数を求める。
- 重要:sizeof(配列名) は配列全体のサイズになる(例外ルール)
使った表や図の説明(まとめ)
- 配列名ルールの表は a と &a[0]、a+i と &a[i] の対応を整理するためのものです。
- 違いの表は「配列そのもの」と「ポインタ変数」を混同しないためのものです。
- アドレスの進み方の図は「iバイトではなく i要素ぶん進む」を直感で理解するためのものです。
- 例外の表は sizeof と & のときだけ挙動が変わることを整理するためのものです。
