徹底図解!C言語のポインタと配列の違いを解説!

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] の部分のみ。一方でポインタはアドレスを格納する変数なので、そのアドレスを変更してやることができ、変更することによりプログラム内で確保したメモリ全てを制御することが可能です。

まとめ

配列名がアドレスを指すので値の参照自体はポインタと同様の方法で行うことが可能です。しかし、ポインタはアドレスを格納する変数であるのに対し、配列名は単なるアドレスの値ですので、指す先(アドレス)を変更することが可能なのはポインタのみです。配列名のアドレスを変更しようとするとコンパイルエラーになります。

ポインタは指す先(アドレス)を変更することで、プログラムで確保したメモリ領域をどこでも制御することが可能ですが、配列名はアドレスを変更できないので、結局その配列部分しか制御することができません。

この辺りがポインタと配列の違いになります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です