ポインタを指すポインタ

最後、もう一度お題を挙げる。

以下の違いを説明せよ。
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