2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C言語でも配列とポインタはぜんぜん違うものだよ

Last updated at Posted at 2020-10-09

これは何?

C言語の配列とポインタがごっちゃになりがちなので、何がどう違うのかを軽く説明する。

前提

配列は、同じ型の値を複数入れる入れ物。

C99
int a[3]; // int 3個。初期値は不定。
int b[] = { 1, 2, 3, 4 }; // 初期化子リストがあると、件数は省略できる。
int c[3] = { 1 }; // 初期化子リストが短いと、残りは 0 で埋められる。

ポインタは、ある値がある場所を指す値。

C99
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 演算子で、その配列全体のサイズ(要素のサイズでも、要素を指すポインタのサイズでもない)を得る。

ぐらいしかない。あともう一つあるけど(後述)。

いや普通に

C99
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つ以外の操作を試みると、先頭の要素へのポインタへと暗黙のうちに変換される」が発動し、いずれもポインタ、それも、配列そのものを指すポインタではなく、先頭の要素を指すポインタに対する演算になる。

つまり前述のコードは、宣言の行以外はすべて 先頭の要素へのポインタへの暗黙の変換 を利用してポインタへの変換が行われたうえで、ポインタに対する操作をしている。

関数の引数にある配列のように見えがちなもの

まれに

C99
void foo( int a[3] );
void bar( double b[]) { /* 略 */ }

のような関数宣言・定義を見ることがあるが、これは完全に

C99
void foo( int * a );
void bar( double * b ) { /* 略 */ }

と同じものである。仮引数の a , b は一見配列っぽく見えるが、単なるポインタである。
ちなみにコンパイラは a[3] に現れる、配列のサイズのように見える 3 を無視する。

配列ではなくポインタである証拠に

C99
void foo( int a[3] ){
    ++a; // a はポインタなので ++ することもできる。
    int ary[3];
    ++ary; // ary は配列なので ++ できず、この行はエラーになる。
}

と、仮引数リストで int a[3] という形で導入された a は、 ++ して値を変えることができる。

このように

このように C言語は、配列は暗黙のうちにポインタに変換されるし、配列のように見えるものの正体がポインタだったりと、配列とポインタがごっちゃになるような文法になっている。

でも最初に書いたとおり、配列とポインタはぜんぜん違うものだ。

まとめ

  • 配列とポインタはぜんぜん違うものだよ。
  • 配列はなにかと暗黙のうちに「先頭の要素を指すポインタ」に変換されるよ。
  • 仮引数リストに配列のように見えるものを書けるけど、それは配列じゃなくてポインタだよ。

私見

C言語の配列は不便でわかりにくい言語要素だと思う。

関数の返戻値にもなれないし、一度定義した配列への代入もできない。そのような性質を持つ合理的理由もないと思う。

配列とポインタの混同をうながすような文法になっていて、多くの人が混乱していると思う。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?