C言語において配列はポインタと同じように操作できてしまうので、ポインタと配列を同じものとして混同している方も多いのではないかと思います。しかし、ポインタと配列には大きな違いがあります。このページではポインタと配列の違いを解説したいと思います。
ポインタと配列の違い
2つの違いがわかるプログラムを書いてみました。
#include<stdio.h>
int main(void){
char array[3] =
{'a', 'b', 'c'};
char *pointer;
pointer = array;
printf("array = %p\n", array);
printf("pointer = %p\n", pointer);
printf("array[2] = %c\n", array[2]);
printf("pointer[2] = %c\n", array[2]);
printf("*array = %c\n", *array);
printf("*pointer = %c\n", *pointer);
printf("&array = %p\n", &array);
printf("&pointer = %p\n", &pointer);
return 0;
}
このプログラムでどのような動きをするのかを図を使って順に解説していきたいと思います。
変数宣言時のメモリ配置
まず、下記の変数宣言です。
char array[3] =
{'a', 'b', 'c'};
char *pointer;
これにより下のようにchar 型3つ分と char* 型1つ分のメモリが確保されます。確保というのは、このプログラムでこのメモリ領域を使用するので他の人(プログラム)はこの領域触らないでねーっていう宣言です。
さらに下記によりポインタ変数 pointer が array[3] の先頭アドレスを指させます。
pointer = array;
図で書くと下のようになります。
さてここで下記によって array と pointer の値を表示してみましょう。
printf("array = %p\n", array);
printf("pointer = %p\n", pointer);
結果は下記のようになりました。
array = 0x7ffeeef93ab9 pointer = 0x7ffeeef93ab9
注目は2つの結果が同じであることです。pointer 変数は array[3] の先頭アドレスを指しています。なので、上記のように array とpointer が同じ値であるということは、配列は配列名だけだと、その配列の先頭アドレスを指すということが分かります。
スポンサーリンク
値の参照は同様の記述で可能
配列名はアドレスを指すため、pointer と array で array 配列の値には同じようにしてアクセスすることが可能です。
例えば下記のような表示を行うこともできます。
printf("array = %p\n", array);
printf("pointer = %p\n", pointer);
printf("array[2] = %c\n", array[2]);
printf("pointer[2] = %c\n", array[2]);
ポインタは変数・配列名は単なるアドレス
さてここまではポインタと配列とで同じ操作ができる点を見てきましたが、ここから本題である2つの違いについて迫っていきたいと思います。
結論から言うとこの2つの違いは、pointer はアドレスを格納する変数であるのに対し、arrayは単なるアドレス(定数)でしかないと言うことです。
試しに下記で pointer と array のアドレスを見てみましょう。
printf("&array = %p\n", &array);
printf("&pointer = %p\n", &pointer);
結果は下記のようになりました。
&array = 0x7ffeeef93ab9 &pointer = 0x7ffeeef93ab0
注目すべき点は &array が arrayと同じ値になっているところです。つまり自分自身が自分自身を指しています。
その一方で、pointer は違うアドレスから array配列の先頭アドレスを指していることになります。つまり、pointer はアドレスを格納するメモリがあるけれど、array に関してはアドレスを格納するメモリはないと言うことです。なので、pointer は値(指す先のアドレス)を格納するメモリ領域があるので変更することができますが、 array に関してはアドレスを格納するメモリ領域はないので値を変更することができません。
試しに下記のプログラムをコンパイルしようとしてもエラーになってしまいます。これは array の値を変更しようとしているためです。
#include <stdio.h>
char globalarray[4] =
{'z', 'y', 'x', 'w'};
char *func1(void);
char *func1(void){
return globalarray;
}
int main(void){
char array[3] =
{'a', 'b', 'c'};
char *pointer;
pointer = array;
printf("array = %p\n", array);
printf("pointer = %p\n", pointer);
printf("array[2] = %c\n", array[2]);
printf("pointer[2] = %c\n", array[2]);
printf("*array = %c\n", *array);
printf("*pointer = %c\n", *pointer);
printf("&array = %p\n", &array);
printf("&pointer = %p\n", &pointer);
array = func1(); /* コンパイルエラー */
pointer = func1();
printf("pointer[2] = %d\n", pointer[2]);
return 0;
}
またポインタを扱う時によく使う下記のようにアドレスを変更しながら表示を行うようなプログラムもコンパイルエラーです。これも array の値を変更しようとしているのが理由です。
array++;
つまり、配列を使って制御できるのは、結局その配列部分のみということになります。このプログラムでいうと array[0] から array[2] の部分のみ。一方でポインタはアドレスを格納する変数なので、そのアドレスを変更してやることができ、変更することによりプログラム内で確保したメモリ全てを制御することが可能です。
まとめ
配列名がアドレスを指すので値の参照自体はポインタと同様の方法で行うことが可能です。しかし、ポインタはアドレスを格納する変数であるのに対し、配列名は単なるアドレスの値ですので、指す先(アドレス)を変更することが可能なのはポインタのみです。配列名のアドレスを変更しようとするとコンパイルエラーになります。
ポインタは指す先(アドレス)を変更することで、プログラムで確保したメモリ領域をどこでも制御することが可能ですが、配列名はアドレスを変更できないので、結局その配列部分しか制御することができません。
この辺りがポインタと配列の違いになります。
オススメの参考書(PR)
C言語一通り勉強したけど「ポインタがよく分からない」「ポインタの理解があやふや」「もっとC言語の理解を深めたい」という方には、下記の「C言語ポインタ完全制覇」がオススメです!
この本の主な内容は下記の通りで、通常の参考書では50ページくらいで解説するポインタを、この本では約 "360ページ" 使って幅広く・深く解説しています。
- C言語でのメモリの使い方
- 配列とポインタの関係性
- ポインタのよくある使い方
- ポインタの効果的な使い方
一通りC言語を学んだだけだと "理解があやふやになってしまいがち" "疑問に思いがち" な内容に対する明確な解説が多いため、特にポインタやC言語の理解があやふやという方にはオススメの本です。
また、C言語においてポインタはまさに "肝" となる機能ですので、ポインタについてより深く学ぶことでC言語全体の理解を深めることにもつながります。
ポインタ・C言語についてより深く理解するための本としては現状1番のオススメの本です。
ただし、他の入門書等で "一通りC言語を学んでいる" 方向けの解説になっているので、"C言語を始めるにあたっての最初の入門書" として利用すると難易度が高いので注意してください。
入門用のオススメ参考書は下記ページで紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/