C言語入門|配列がポインタになる3つの理由

「え、配列を渡しただけなのに、なんでポインタ扱いになるの?」
C言語を学んでいると、ここで一度は頭がフリーズします。

しかも厄介なのは、

  • ソースコードにはポインタなんて書いてない。
  • コンパイルエラーも出ない。
  • でも動きは明らかにポインタ渡し。

という、静かに罠が仕掛けられている感じ

でも安心してください。
これは設計ミスでもバグでもなく、C言語が最初から持っている仕様です。

今回は
「なぜ配列はポインタになるのか?」
その理由を 3つのからくり構文 に分けて、順番にほどいていきます。

まず確認しておきたいサンプルプログラム

最初に、配列を引数に取る関数を呼び出す、シンプルな例を見てみましょう。

プロジェクト名:10-3-1 ソースファイル名: sample10-3-1.c

#include <stdio.h>

void update(int data[4])
{
    data[0] = 99;
}

int main(void)
{
    int values[4] = {10, 20, 30, 40};

    update(values);

    printf("values[0]=%d\n", values[0]);
    return 0;
}

実行結果

values[0]=99

関数 update の中で data[0] を書き換えただけなのに、
main関数の配列までしっかり書き換わっています。

ここからが本題です。

理由① 関数宣言の特殊構文

まず最初の仕掛けが、関数宣言そのものにあります。

C言語には、次のルールがあります。

特殊構文① 関数宣言の解釈

関数の引数や戻り値に配列型を指定すると、
ポインタ型を指定したものと見なされる。

つまり、先ほどの関数宣言はこう読まれます。

void update(int* data)
{
    data[0] = 99;
}

実質的に同じと見なされる引数宣言

書き方実際の意味
int data[4]int* data
int data[]int* data
int* dataint* data

添え字の 4 は 見た目の安心材料であって、型としては無視されます。

「勝手に型を変えられてるのに警告なし!?」
そう思うのが普通ですが、これがC言語の仕様です。

理由② 配列変数評価の特殊構文

「引数がポインタ扱いなのはわかった。
でも、呼び出すときは配列をそのまま渡してるよね?」

ここで登場するのが、2つ目の仕掛けです。

特殊構文② 配列変数の評価

ソースコード上に書かれた配列変数名は、
配列の先頭要素のアドレスに化ける。

つまり、この呼び出しは

update(values);

実際には、次と同じ意味になります。

update(&values[0]);

普通の変数との違い

種類アドレスを渡す書き方
通常の変数&x
配列配列名だけでOK

配列だけが、暗黙的にアドレスに変換される特別扱いなのです。

理由③ 添え字演算子の特殊構文

「でもさ、updateの中で data[0] って書いてるよ?
ポインタなのに添え字って使っていいの?」

はい、そこが3つ目の仕掛けです。

特殊構文③ 添え字演算子の評価

ポインタ p があるとき、次はすべて同じ意味になります。

書き方意味
p[0]*p
p[1]*(p + 1)
p[N]*(p + N)

つまり、関数内のこのコードは

data[0] = 99;

実質的に、次と同じです。

*data = 99;

イメージとして理解する

  • data は 配列の先頭アドレス
  • *data は その場所にある int
  • data[0] も 同じ場所を指す

だから、配列っぽく書いていても、
中身は完全にポインタ操作なのです。

3つの特殊構文が同時に働いた結果

ここまでをまとめると、配列を関数に渡した瞬間に次が起きています。

段階実際に起きていること
宣言配列引数はポインタに読み替え
呼び出し配列名が先頭アドレスに変換
操作添え字がポインタ演算に変換

この3点セットが揃った結果、

配列は必ずポインタ渡しになる

という挙動が完成します。

なぜ独立性が崩れるのか

ポインタ渡しの最大の特徴はこれです。

ポインタ渡しによる独立性の崩壊

呼び出し元と呼び出し先が
同じメモリ番地を共有する。

つまり、

  • main関数の配列
  • 関数内の data

は、同じ実体を見ています。

だから、関数内で書き換えると、
呼び出し元の配列も当然変わるのです。

丸暗記でいいもの、理解すべきもの

正直に言うと、

  • 特殊構文①
  • 特殊構文②

この2つは 仕様として覚えるしかありません

でも、

  • 特殊構文③(添え字とポインタの関係)

これは、理解しておくと一気にC言語が読みやすくなります。

ここを理解できると、

  • 配列
  • ポインタ
  • 関数引数

が、一本の線でつながって見えるようになります。