先日プログラミングの勉強がてら、ソースコードを書いていましたが、「assignment from incompatible pointer type [-Wincompatible-pointer-types]」という警告を出してしまいました。
そのときのソースコードがこちらです。(かなり抜粋しています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include<stdio.h> int main(void) { int array[3] = {1, 2, 3}; int *ptr; int i; ptr = &array; for(i = 0; i < 3; i++){ printf("*ptr...%d\n",*(ptr + i)); } return 0; } |
問題は、「ptr = &array;」の部分ですね。
本当は配列arrayの先頭アドレスを入れたかったのですが、”array”もしくは”&array[0]”としなければいけないところを、間違えてしまったのが原因です。
しかもすごくややこしいことに、arrayの先頭アドレスの入れ方を間違えているのに、結果は意図通り、「1,2,3」と出力されてしまいます。
問題となる箇所の解決方法はすぐに分かったのですが、”&array”が何のポインタを指しているのかが分からず、色々調査していました。
そして自分的に納得できたので、メモがてら残しておきます。
【前提】警告の意味について
まずは「assignment from incompatible pointer type [-Wincompatible-pointer-types]」という警告について理解しておきましょう。
すでにこの警告が、どういう意味か分かっている人は無視してください。
この警告を日本語訳すると、「互換性のないポインター型からの代入」という意味になるそうです。
要は、「左辺と右辺でポインタの型が違うよ!」という意味ですね。
分かりやすい例でいくと、以下のような代入を行っていることになります。
1 2 3 4 5 |
int *int_ptr; /* int型へのポインタ */ double double_array[3]; /* double型の配列 */ /* int型へのポインタ変数にdouble型の配列の先頭アドレスを代入 */ int_ptr = &double_array[0]; /* ←型が異なるのでwarningが出力される */ |
左辺はint型で右辺はdouble型で、型がそれぞれ異なっています。これをコンパイルすると、やはり同様のwarningが出てきました。
「&array」の型は一体何なのか?
warningの意味は、左辺と右辺で型が違うためであるということが分かりました。
しかし、一番最初のソースコードだと、どちらもint型になっています。
1 2 3 4 5 |
int array[3] = {1, 2, 3}; int *ptr; /* どちらもint型だけどなぜwarningが出る...? */ ptr = &array; |
“&array”の型について解決するためには、まずは”&array”が指しているアドレスについて理解する必要があります。
“&array”が指しているアドレスについて
“&array”が指しているのは、「配列array”そのもの”のアドレス」です。
「*ptr = &array」と代入しても結果は意図通りになりましたが、結局はarrayの先頭アドレスを指していたからです。
整理するとこんな感じです。
1 2 3 |
ptr = &array[0]; /* arrayの先頭アドレス */ ptr = array; /* arrayの先頭アドレス */ ptr = &array; /* arrayそのもののアドレス */ |
こんな感じで”&array”としたときだけニュアンスが変わりますが、結局は同じアドレスを指しています。
「配列そのもののアドレス」とは?
「配列そのもののアドレス」と「配列の先頭アドレス」は同じアドレスを指しますが、何が違うのでしょうか?
それはポインタ演算をすると、ハッキリ分かります。
例えば、以下のコードを見て下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main(void){ char array[3] = {1, 2, 3}; char *ptr; char (*ptr2)[3]; ptr = &array[0]; ptr2 = &array; /* 配列の先頭へのポインタ */ printf("ptr...%p\n",ptr); ptr++; /* アドレスをインクリメント */ printf("ptr...%p\n",ptr); /* 配列そのものへのポインタ */ printf("ptr2...%p\n",ptr2); ptr2++; /* アドレスをインクリメント */ printf("ptr2...%p\n",ptr2); return 0; } |
「配列の先頭へのポインタ(char型)」と「配列そのものへのポインタ(char型)」をそれぞれインクリメントさせてみました。
すると私の環境では、以下の結果になりました。
それぞれ異なる結果になっていますね。
先頭アドレスを指している方は、インクリメントすると、アドレスは”1″増えましたが、配列そのもののアドレスを指している方は、”3″増えました。
ポインタをインクリメントすると、型のサイズ分だけ増えます。char型は1バイトなので、アドレスも”1″増えました。
問題は、配列そのものを指しているポインタの方です。
これは配列そのものを指しているので、配列の大きさ分(型のサイズ × 配列の要素分)増えていることになります。
今回であれば要素数が”3″でしたので、型のサイズ(1) × 配列の要素(3) = 3バイト分、サイズが増えたということですね。
もし要素数が”10″であれば、アドレスも同様に”10″増えることになります。
“&array”の型について
“&array”は配列そのものへのアドレスを指していることが分かりました。
配列全体を指していることになるので、型は「int型の配列へのポインタ」になりますので、以下のように代入するのが正しいです。
1 2 3 4 5 |
int array[3] = {1, 2, 3}; int (*ptr)[3]; int i; ptr = &array; |
ptrの宣言を「(*ptr)[3]」と変えました。これが配列(要素数3)へのポインタの型であり、warningも無事出なくなりました。
一旦、ここでまとめておきましょう。
- “&array”が指しているのは、配列arrayそのもののアドレス
- 型は「xxx型の配列へのポインタ」になるので、int型の配列であれば「int (*ptr)[3]」のような変数に代入する必要がある
配列へのポインタの使い道は?
“&array”の正体は、「配列へのポインタ」ということが分かりました。
しかし、それをどこで使うのかが正直分からなかったので、こちらについても調査しました。
結論、二次元配列が出てきたときに使用することが出来ます。ただ、以下のような書き方はほとんどしないので、あまり使用しないと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include<stdio.h> void func(int (*ptr)[3]); int main(void) { int array[2][3] = { {1, 2, 3}, {4 ,5 ,6} }; func(&array[0]); return 0; } void func(int (*ptr)[3]){ int i,j; for(i = 0; i < 2; i++) { for(j = 0; j < 3; j++) { printf("*ptr...%d\n",(*(ptr + i))[j]); } } } |
func(&array[0])の部分は、「array(要素数3)の配列そのもののアドレス」を引数として設定しています。
ですので、func関数内の「*(ptr + i)」の部分は、(i * 3)サイズ分だけ、アドレスが移動します。
まとめ
「&array」と誤って記載したときに、何が起こっているのかがあまり分からなかったので、自分なりにまとめました。
まぁ配列そのもののアドレスを意識して使うことはないのかなと、調べている中で思いました。
ただ、どういう動きをしているかは大事だと思うので、覚えておきます。