ポインタを指すポインタ
最後、もう一度お題を挙げる。
以下の違いを説明せよ。 1)int a[2][3]; 2)int a[3][2]; 3)int *a[3]; 4)int (*a)[3]; 5)int **a;
最後にポインタのポインタ。俗にダブルポインタなどと言ったりする。4)に比べれば、割となじみがある。呼び出し先で、ポインタに値をセットしたい場合などに登場する。また、3)の配列名のみを式中に書いた場合の型が、5)と同じになっている。mainの第二引数は正にこの型になっている。
サンプルコードは、3)と比べやすいようにcharでコーディングする。
#include <stdio.h> #include <stdlib.h> int main(void) { char **p; int i, j; /* バッファ確保 */ p = malloc(sizeof(char *) * 2); for (i = 0; i < 2; i++) { p[i] = malloc(3); } printf("char **p (Pointer to Pointer to char)\n"); /* サイズ確認 */ printf("sizeof(p) = %u, sizeof(p[0]) = %u\n", sizeof(p), sizeof(p[0])); /* アドレス確認 */ for (i = 0; i < 2; i++) { printf(" p[%d] is @ %p\n", i, p[i]); } printf("====================== \n"); for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { printf("p[%d][%d] is @ %p\n", i, j, &p[i][j]); } } /* メモリ解放 */ for (i = 0; i < 2; i++) { free(p[i]); } free(p); return 0; }
実行結果
$ ./main char **p (Pointer to Pointer to char) sizeof(p) = 4, sizeof(p[0]) = 4 p[0] is @ 0x6d1fb8 p[1] is @ 0x6d1fc8 ====================== p[0][0] is @ 0x6d1fb8 p[0][1] is @ 0x6d1fb9 p[0][2] is @ 0x6d1fba p[1][0] is @ 0x6d1fc8 p[1][1] is @ 0x6d1fc9 p[1][2] is @ 0x6d1fca
- char[2][3]なら6バイトの連続領域が確保されるが、4byte(sizeof(char *))×2、3バイト(sizeof(char)*3)×2が別々に確保される。→3)と同じようなメモリ配列になっている。
- ポインタなので、p, p[0]ともサイズは4になる。
1)〜5)の違いを理解できてない内に、動的にchar[2][3]を確保して使うために下のようなコードを書いて、Segmentation Faltで落ちてしまって首をひねるというのは、誰でもすると思う。(私だけじゃないよね?)
char **p; p = malloc(2 * 3); // 6バイト(sizeof(char) 2 * 3)分のバッファを確保 p[0][0] = 0; // どこにアクセスする?
pの型はchar**, p[0]の型はchar*である。mallocした段階ではゴミが入っているので、p[0][0]はどこにアクセスするかわからない。6byteしか確保していないので、p[1]の時点で、すでに確保されていない領域に踏み込んでいる。
確認のためのサンプルコード
※注意:上記のようにp[1]からは確保していない領域なので、動作は未定義である。
#include <stdio.h> #include <stdlib.h> int main(void) { char **p; int i, j; /* バッファ確保 */ p = malloc(sizeof(char) * 2 * 3); printf("char ** => char[2][3]?\n"); /* サイズ確認 */ printf("sizeof(p) = %u\n", sizeof(p)); /* アドレス確認 */ for (i = 0; i < 2 * 3; i++) { printf(" p[%d] is @ %p\n", i, p[i]); } printf("====================== \n"); for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { printf("p[%d][%d] is @ %p\n", i, j, &p[i][j]); } } /* メモリ解放 */ free(p); return 0; }
実行結果
※注意:p[1]以降へのアクセス動作は未定義である。
$ ./main char ** => char[2][3]? sizeof(p) = 4, sizeof(p[0]) = 4 p is @ 0x6d1eb8 p[0] is @ 0x0 p[1] is @ 0x0 p[2] is @ 0x0 p[3] is @ 0x33 p[4] is @ 0x611c2f68 p[5] is @ 0xdf0df046