これは何?
C言語の配列とポインタがごっちゃになりがちなので、何がどう違うのかを軽く説明する。
前提
配列は、同じ型の値を複数入れる入れ物。
int a[3]; // int 3個。初期値は不定。
int b[] = { 1, 2, 3, 4 }; // 初期化子リストがあると、件数は省略できる。
int c[3] = { 1 }; // 初期化子リストが短いと、残りは 0 で埋められる。
ポインタは、ある値がある場所を指す値。
int a=3;
int * pa = &a; // pa は a を指す。
int ** ppa = &pa; // ppa は pa を指す。
int * p = (int*)malloc(10*sizeof(int)); // p は 10個のintを格納する領域の先頭を指すだろう。
このとおり、配列はポインタじゃないし、ポインタは配列じゃない。
似たものですらない。
操作
ポインタに対する操作
ポインタに対しては
- 間接演算子(
*
)で、ポインタが指している値(の左辺値)を得る。 - 整数を加減して、近所のオブジェクトを指すようにする。
- 同じ型の値を指す近所のポインタを減じて、そのポインタとの距離のような値を得る。
- アドレス演算子(
&
)で、そのポインタを指すポインタを得る。 -
sizeof
演算子で、そのポインタのサイズ(ポインタが指しているオブジェクトのサイズではない)を得る。 - 代入演算子で、右辺から左辺に代入する。単なる代入だけではなく、
+=
なんかもできる。 - 代入演算子がなくても、
some_func(pa);
みたいな形で、他の関数にポインタを渡すことができる。 - ポインタの指す先が関数の場合、
pa(1,2)
みたいに、(1,2)
のようなものをつけると関数呼び出しになる。
など、様々な操作ができる。
ポインタ周辺のシンタックスシュガー
ポインタに対してよく使われるのシンタックスシュガーが2つある。
ひとつは a->b
。これは (*a).b
のこと。
もうひとつは a[b]
。これは *(a+(b))
のこと。
有名な話だけど、 1[p]
は *(1+p)
なので p[1]
と同じ意味になる。
配列に対する操作
一方。
配列に対してできる操作は限られている。
- アドレス演算子(
&
)で、その配列を指すポインタを得る。 -
sizeof
演算子で、その配列全体のサイズ(要素のサイズでも、要素を指すポインタのサイズでもない)を得る。
ぐらいしかない。あともう一つあるけど(後述)。
いや普通に
int a[3]={1,2,3}; // これは定義。操作じゃない。
*a = 123; // 間接演算子で先頭要素へのアクセスを得る
int b = a[1]; // シンタックスシュガー [] の適用
int * p = a+1; // 二項演算子 `+` の適用
int * q = a; // 代入演算子の右辺になる
printf( "%p\n", a ); // 関数に渡す
のように書けるじゃないか、と思うかもしれないけど、いずれの操作も実は配列に対する演算ではない。
C言語の配列に対するスペシャルなルール「前述の2つ以外の操作を試みると、先頭の要素へのポインタへと暗黙のうちに変換される」が発動し、いずれもポインタ、それも、配列そのものを指すポインタではなく、先頭の要素を指すポインタに対する演算になる。
つまり前述のコードは、宣言の行以外はすべて 先頭の要素へのポインタへの暗黙の変換 を利用してポインタへの変換が行われたうえで、ポインタに対する操作をしている。
関数の引数にある配列のように見えがちなもの
まれに
void foo( int a[3] );
void bar( double b[]) { /* 略 */ }
のような関数宣言・定義を見ることがあるが、これは完全に
void foo( int * a );
void bar( double * b ) { /* 略 */ }
と同じものである。仮引数の a
, b
は一見配列っぽく見えるが、単なるポインタである。
ちなみにコンパイラは a[3]
に現れる、配列のサイズのように見える 3
を無視する。
配列ではなくポインタである証拠に
void foo( int a[3] ){
++a; // a はポインタなので ++ することもできる。
int ary[3];
++ary; // ary は配列なので ++ できず、この行はエラーになる。
}
と、仮引数リストで int a[3]
という形で導入された a
は、 ++
して値を変えることができる。
このように
このように C言語は、配列は暗黙のうちにポインタに変換されるし、配列のように見えるものの正体がポインタだったりと、配列とポインタがごっちゃになるような文法になっている。
でも最初に書いたとおり、配列とポインタはぜんぜん違うものだ。
まとめ
- 配列とポインタはぜんぜん違うものだよ。
- 配列はなにかと暗黙のうちに「先頭の要素を指すポインタ」に変換されるよ。
- 仮引数リストに配列のように見えるものを書けるけど、それは配列じゃなくてポインタだよ。
私見
C言語の配列は不便でわかりにくい言語要素だと思う。
関数の返戻値にもなれないし、一度定義した配列への代入もできない。そのような性質を持つ合理的理由もないと思う。
配列とポインタの混同をうながすような文法になっていて、多くの人が混乱していると思う。