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 + ii個後ろの要素へのポインタ&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 と & のときだけ挙動が変わることを整理するためのものです。