C言語

【C言語】「&array」配列へのポインタは何者なのか?

先日プログラミングの勉強がてら、ソースコードを書いていましたが、「assignment from incompatible pointer type [-Wincompatible-pointer-types]」という警告を出してしまいました。

そのときのソースコードがこちらです。(かなり抜粋しています)

問題は、「ptr = &array;」の部分ですね。

本当は配列arrayの先頭アドレスを入れたかったのですが、”array”もしくは”&array[0]”としなければいけないところを、間違えてしまったのが原因です。

しかもすごくややこしいことに、arrayの先頭アドレスの入れ方を間違えているのに、結果は意図通り、「1,2,3」と出力されてしまいます。

問題となる箇所の解決方法はすぐに分かったのですが、”&array”が何のポインタを指しているのかが分からず、色々調査していました。

そして自分的に納得できたので、メモがてら残しておきます。

【前提】警告の意味について

まずは「assignment from incompatible pointer type [-Wincompatible-pointer-types]」という警告について理解しておきましょう。

すでにこの警告が、どういう意味か分かっている人は無視してください。

この警告を日本語訳すると、「互換性のないポインター型からの代入」という意味になるそうです。

要は、「左辺と右辺でポインタの型が違うよ!」という意味ですね。

分かりやすい例でいくと、以下のような代入を行っていることになります。

左辺はint型で右辺はdouble型で、型がそれぞれ異なっています。これをコンパイルすると、やはり同様のwarningが出てきました。

「&array」の型は一体何なのか?

warningの意味は、左辺と右辺で型が違うためであるということが分かりました。

しかし、一番最初のソースコードだと、どちらもint型になっています。

“&array”の型について解決するためには、まずは”&array”が指しているアドレスについて理解する必要があります。

“&array”が指しているアドレスについて

“&array”が指しているのは、「配列array”そのもの”のアドレス」です。

「*ptr = &array」と代入しても結果は意図通りになりましたが、結局はarrayの先頭アドレスを指していたからです。

整理するとこんな感じです。

こんな感じで”&array”としたときだけニュアンスが変わりますが、結局は同じアドレスを指しています。

「配列そのもののアドレス」とは?

「配列そのもののアドレス」と「配列の先頭アドレス」は同じアドレスを指しますが、何が違うのでしょうか?

それはポインタ演算をすると、ハッキリ分かります。

例えば、以下のコードを見て下さい。

「配列の先頭へのポインタ(char型)」と「配列そのものへのポインタ(char型)」をそれぞれインクリメントさせてみました。

すると私の環境では、以下の結果になりました。


それぞれ異なる結果になっていますね。

先頭アドレスを指している方は、インクリメントすると、アドレスは”1″増えましたが、配列そのもののアドレスを指している方は、”3″増えました。

ポインタをインクリメントすると、型のサイズ分だけ増えます。char型は1バイトなので、アドレスも”1″増えました。

問題は、配列そのものを指しているポインタの方です。

これは配列そのものを指しているので、配列の大きさ分(型のサイズ × 配列の要素分)増えていることになります。

今回であれば要素数が”3″でしたので、型のサイズ(1) × 配列の要素(3) = 3バイト分、サイズが増えたということですね。

もし要素数が”10″であれば、アドレスも同様に”10″増えることになります。

“&array”の型について

“&array”は配列そのものへのアドレスを指していることが分かりました。

配列全体を指していることになるので、型は「int型の配列へのポインタ」になりますので、以下のように代入するのが正しいです。

ptrの宣言を「(*ptr)[3]」と変えました。これが配列(要素数3)へのポインタの型であり、warningも無事出なくなりました。

一旦、ここでまとめておきましょう。

「&array」の正体について
  • “&array”が指しているのは、配列arrayそのもののアドレス
  • 型は「xxx型の配列へのポインタ」になるので、int型の配列であれば「int (*ptr)[3]」のような変数に代入する必要がある

配列へのポインタの使い道は?

“&array”の正体は、「配列へのポインタ」ということが分かりました。

しかし、それをどこで使うのかが正直分からなかったので、こちらについても調査しました。

結論、二次元配列が出てきたときに使用することが出来ます。ただ、以下のような書き方はほとんどしないので、あまり使用しないと思います。

func(&array[0])の部分は、「array(要素数3)の配列そのもののアドレス」を引数として設定しています。

ですので、func関数内の「*(ptr + i)」の部分は、(i * 3)サイズ分だけ、アドレスが移動します。

まとめ

「&array」と誤って記載したときに、何が起こっているのかがあまり分からなかったので、自分なりにまとめました。

まぁ配列そのもののアドレスを意識して使うことはないのかなと、調べている中で思いました。

ただ、どういう動きをしているかは大事だと思うので、覚えておきます。