はじめての C

Arrays and Pointers in C

なぜ ダブルポインタは、2次元配列として 使えないのか ?

宣言としては まちがいなので、たぶん コンパイラは クレームを つけますが、これは その 1つの いい例です、

"int **mat" として、"mat" を 2次元配列に 用いる。

この 2つは まったく 違った データ型なので、使用すると、メモリ上の 異なる 場所に アクセスします。

すぐれた マシン (例えば VAX/VMS のような) では、このまちがいでは、「不正な メモリアクセス」と エラーを だして 強制終了します。

こうした まちがいは よく 目にします。 というのも、減算変換は 同じ 配列へは (2度以上は) 再帰的に 適用されることは なく、2次元配列が ダブルポインタと 同じで 「ない」ことを、すぐに 忘れてしまうからです。

「T型の ポインタへの ポインタ」は 「T型の 2次元配列」としての 役割は 果たすことが できません。

2次元配列は 「T型の 列 row への ポインタ」と 「同じ」ですので、それは 「T型の ポインタへの ポインタ」とは まったく 異なるのです。

ダブルポインタが 配列の 最初の 要素を 指すとき、"ptr[0][0]" という 添字表記として 使われていて、それは 確実に 2回、間接参照されるのです (ルール 5 を 参照)。

きちんと 2度 間接参照した後、生成される オブジェクトの もつ アドレスは、どんなときも 配列の 最初の 要素の 「内部を」 指す 値と 同じになります。

最初の 要素には データも 含まれるので、デタラメな メモリに アクセスすることに なるのです。

「T型への ポインタ」という 中継ぎをもつ 特別な 間接参照に、注意を 向けて見ましょう。

   type mat[m][n], *ptr1, **ptr2;
ptr2 = &ptr1;
ptr1 = (type *)mat;

でも、どちらも うまく 動かないでしょう。 配列の "width 幅" (n) の 中の 情報が 失われ、右側の それだけが 得られて、再び デタラメな メモリに アクセスすることに なります。

ダブルポインタを 2次元配列のように 動かす 可能な 方法の 1つは、それぞれが 元の 行列 matrix の 列 row を 指すような、補助となる ポインタ配列を もつことです。

   type mat[m][n], *aux[m], **ptr2;
ptr2 = (type **)aux;

for (i = 0; i < m; i++)
aux[i] = (type *)mat + i * n;

当然、この 補助となる 配列は 動的なものと なります。

サンプル プログラムは、

#include 
#include

main()
{
long mat[5][5], **ptr,

mat[0][0] = 3;
ptr = (long **)mat;

printf("mat %p\n", mat);
printf("ptr %p\n", ptr);
printf("mat[0][0] %d\n", mat[0][0]);
printf("&mat[0][0] %p\n", &mat[0][0]);
printf("&ptr[0][0] %p\n", &ptr[0][0]);

return;
}

VAX/VMS での、出力の 結果は、

   mat        7FDF6310
ptr 7FDF6310
mat[0][0] 3
&mat[0][0] 7FDF6310
&ptr[0][0] 3

"mat""ptr" とは 同じ 値にも かかわらず、"mat[0][0]""ptr[0][0]" が (異なる アドレスを もつ) 別の オブジェクトだと いうことが わかります。



(追記) ぼくの 環境では、出力は 次のように なりました、

   mat        0xbffff868
ptr 0xbffff868
mat[0][0] 3
&mat[0][0] 0xbffff868
&ptr[0][0] 0x3
(ちょっと 訂正) VMS って DEC の 製品名だったのか ...