C言語の配列とポインタについて

379, 2021-12-30

目次

C言語の配列とポインタについて

C言語の配列はポインタに代入することができます
配列をポインタに入れることでプログラミングが便利になる側面があります。
いっぽう、配列とポインタを同一視してしまうと、思わぬ落とし穴に落ちる場合もあります

この記事では配列とポインタについて具体的に解説していきます。

配列をポインタに代入する

C言語の配列は、たとえばint型の配列の場合は↓のように定義します。

int array[4] = { 1, 2, 3, 4 };

上記の配列arrayは要素数が4で、各要素には1, 2, 3, 4の値がセットされています。
この配列をポインタ変数に代入するには↓のように書きます。

int array[4] = { 1, 2, 3, 4 };
int *p = array;  // ポインタ変数pにarrayを代入

上記のようにint型のポインタ変数pにarrayを代入することができます。
こうするとpにはarrayの先頭アドレスが代入されます
arrayの先頭アドレスとは、配列の第1要素のアドレスのことを指します。

↓のようなコードを書くと配列とポインタのアドレス値を確認することができます。

#include <stdio.h>

int main(void) {
    int array[4] = { 1, 2, 3, 4 };
    int *p = array;

    printf("array アドレス値: %p\n", array);
    printf("&array[0] アドレス値: %p\n", &array[0]);
    printf("p 代入したアドレス値: %p\n", p);

    return 0;
}

筆者の環境では上記のコードを実行すると↓のように3つとも同じアドレスになります。

array アドレス値: 0x7fff6fc65c60
&array[0] アドレス値: 0x7fff6fc65c60
p 代入したアドレス値: 0x7fff6fc65c60

配列とポインタの演算

C言語の配列は角カッコの中に添え字を書くと、その添え字の要素を参照することができます。

int array[4] = { 1, 2, 3, 4 };

printf("%d\n", array[0]);  // 1
printf("%d\n", array[1]);  // 2
printf("%d\n", array[2]);  // 3
printf("%d\n", array[3]);  // 4

配列を代入したポインタでもこれは同じです。
↓のようにアクセスできます。

int array[4] = { 1, 2, 3, 4 };
int *p = array;

printf("%d\n", p[0]);  // 1
printf("%d\n", p[1]);  // 2
printf("%d\n", p[2]);  // 3
printf("%d\n", p[3]);  // 4

これは配列とポインタの型が同じだからできることです。
int *pintを別の型にしてしまうと、期待した動作は得られません。
配列をポインタに代入するときは、型に注意しましょう。

2次元配列とポインタ

2次元配列の場合です。
1次元配列のポインタが「int *p」なので、2次元配列は「int **pp」じゃないか?
と考えるのが普通ですが、これは間違いです
正確には「int **pp」は動的に確保した2次元配列のポインタとしては正しく機能します。
しかしローカル変数などで確保された2次元配列は↓のようなポインタを用意する必要があります。

int (*pp)[2次元目の要素数]

コードにすると↓のようになります。

#include <stdio.h>

int main(void) {
    int matrix[2][4] = {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
    };
    int (*pp)[4] = matrix;  // 2次元配列のポインタpp

    printf("pp[0][0] = %d\n", pp[0][0]);  // 1
    printf("pp[0][1] = %d\n", pp[0][1]);  // 2

    printf("pp[1][0] = %d\n", pp[1][0]);  // 5
    printf("pp[1][1] = %d\n", pp[1][1]);  // 6

    return 0;
}

ppは普通に2次元配列のようにアクセスすることができます。

動的配列をポインタで確保する

stdlib.h」をインクルードすると使えるmalloc()calloc()などの関数は、ヒープから動的にメモリを確保する関数です。
このときも配列の表現としてポインタを使います
たとえば1次元配列を動的に確保する場合は↓のようにコードを書きます。

int byte = sizeof(int);  // int型のバイト数
int size = byte * 4;  // 4要素の配列を確保する
int *array = malloc(size);
if (!array) {
    // error
}
free(array);


int array[] = malloc(size);」ではないので注意してください。
malloc()などで確保した配列は利用が終わったらfree()関数でメモリを解放しておく必要があります。
これを忘れるとメモリリークと言うバグになります。

上記のように確保した動的な配列は↓のように要素にアクセスすることができます。

array[0] = 1;
array[1] = 2;
printf("%d\n", array[0]);  // 1

sizeof演算子と配列とポインタ

配列とポインタをコードの中で一緒に使う場合に注意したいのがsizeof演算子の挙動の違いについてです。

普通の配列は、sizeofで配列全体のバイト数が求まります。

#include <stdio.h>

int main(void) {
    int array[4];  // int型(4バイト)の要素が4つなので合計で16バイト
                   // ちなみにint型のサイズは環境依存です

    printf("%d\n", sizeof array);  // 16

    return 0;
}

上記の配列arrayint型(4バイト)の要素を4つ持つ配列なので、その全体のサイズは4 × 4で16バイトです。
*int型のサイズは環境依存です

いっぽう、ポインタ変数にsizeofを使うと、ポインタ変数のバイト数が求まります。

#include <stdio.h>

int main(void) {
    int array[4];
    int *p = array;  // arrayへのポインタ

    printf("%ld\n", sizeof p);  // 8

    return 0;
}

配列のサイズを求める場合のトラブルでよくあるのが、sizeof配列のサイズを求めたと思ったらポインタのサイズだった、というトラブルです。
これは非常にバグになりやすいC言語の仕様で、このことは頭のどこかに置いておく必要があります。

関数の引数の配列とsizeof演算子

関数の引数に配列を渡す場合です。
↓のように関数の引数を定義できます。

void func(int array[]) {
    printf("%d\n", array[0]);
}

上記のようにint array[]と書くと1次元配列arrayを引数に取ることが出来ます。

ここで注意したいのが、ふたたびsizeofです。
上記のような引数の定義だと、引数arrayは一見すると配列に見えます。
そのためsizeofで配列のサイズが求まると勘違いしがちです。
しかし実際にはポインタのサイズが求まります

#include <stdio.h>

void func(int array[]) {
    printf("%ld\n", sizeof array);  // 8
}

int main(void) {
    int array[4];  // 16バイトの配列array
    func(array);  // arrayをfuncに渡す
    return 0;
}

上記のコードをコンパイルすると、GCCでは↓のような警告が出力されます。

sizeof2.c: In function ‘func’:
sizeof2.c:4:28: warning: ‘sizeof’ on array function parameter ‘array’ will return size of ‘int *’ [-Wsizeof-array-argument]
    4 |     printf("%ld\n", sizeof array);  // 8
      |                            ^~~~~
sizeof2.c:3:15: note: declared here
    3 | void func(int array[])
      |           ~~~~^~~~~~~

warning: ‘sizeof’ on array function parameter ‘array’ will return size of ‘int *’」という英文は和訳すると、
「警告: 関数の引数arrayにsizeofを使うと、int *のサイズが求まります」となります。
つまり引数arrayはポインタですよ、ということです。

この辺はC言語のややこしい所なので、これも頭の片隅に置いておくと良いかもしれません。

おわりに

今回はC言語の配列とポインタについて解説しました。
配列とポインタはプログラムの中で両方ともよく使われる機能です。
使う場合はsizeofの注意点などを踏まえておきましょう。

(^ _ ^)

配列とポインタはマブダチ

(・ v ・)

切っても切れない関係



この記事のアンケートを送信する