2次元配列の引数の渡しかた

[戻る]
Puppy 2002/04/24(水) 09:28:27
Puppyです。 基礎のところなんですけど、教えて下さい。

int hai[5][10]; という配列があって

Func(&hai[3][0]);
Func(&hai[4][0]); というように関数を呼び出して、この関数内で

hai[3][5]とか、hai[4][5]のデータに変更を加えたいのです。

void Func(int *ptr[])
{
 int x;
 x = ptr[5] + 10;
 ptr[5] = x;

}

とすると、エラーが出てコンパイルがとおりません。
int * から int *[]に変換できません と怒られました。(T-T;

どうしたらうまくいくでしょうか?
C言語の本は何冊か持ってるんですけど、こーゆーことは出ていません。
よろしくお願いします。m(..)mぺこり

Puppy 2002/04/24(水) 10:04:39
Puppyです。
下記のページにのっていました。(*^-^*;
お騒がせしました。

http://www.ht-net21.ne.jp/~sgwr-t/sec11-2.htm

Puppy 2002/04/24(水) 10:05:31
[[解決]]
チェック忘れました。m(..)m

dairygoods 2002/04/24(水) 10:19:16
hai
これは、2次元配列全体を指すポインタ。

hai[3]
これは、3行目((3,0)-(3,10))の1次元配列へのポインタ。

&hai[3][5]
これは、位置(3,5)のintへのポインタ。

でも実は、全部 int* です。
何次元の配列であっても、そのポインタは
単に先頭要素へのポインタです。

YuO [E-Mail] 2002/04/24(水) 11:07:12
>でも実は、全部 int* です。

違います。
hai自体はint [5][10]型で,そのままint [10]へのポインタにはなりますが,
int *にはなりません。

つまり,

int (*p)[10] = hai; /* int [5][10]→int (*)[10] */
は正しいですが,
int *p = hai;
は正しくないです。

dairygoods 2002/04/24(水) 11:56:42
>>でも実は、全部 int* です。

>違います。

すみません。間違いでした。
1次元のサイズがわからなくなってしまいますね。

Puppy 2002/04/24(水) 12:51:27
dairygoodsさん YuOさん ありがとうございます。

>int *p = hai;
>は正しくないです。

私はいまだに理解できません。
できたら、例をあげて教えてもらえませんか?m(..)mペコリ

hai は &hai[0][0] と同じであり、 int型のポインタだと思っています。

間違えでしょうか?

>1次元のサイズがわからなくなってしまいますね。

この意味もわかりませんですぅ。(T-T;

YuO [E-Mail] 2002/04/24(水) 13:15:16
>hai は &hai[0][0] と同じであり、 int型のポインタだと思っています。
>間違えでしょうか?

間違いです。


int hai[5][10];
という宣言は,
typedef int T1[10]; // T1は10個のintからなる配列の型
T1 hai[5]; // haiは5個のT1からなる配列
と同じです。 … (a)

任意の型Tと整定数nがあって,
T array[n]; // arrayはn個のTからなる配列
の時,必要に応じて(というか,ほとんどの場合),
array // arrayの型は,n個のTからなる配列
は
&array[0] // array[0]の型はT,&array[0]の型はTへのポインタ
となります。 … (b)


よって,(a)と(b)から,
hai // haiの型は5個のT1からなる配列
は
&hai[0] // hai[0]の型はT1,&hai[0]の型はT1へのポインタ
となります。

haiがint *になるためには,もう一度(b)を適用する必要があります。
ところが,一度(b)を適用するためには,&hai[0]が配列である必要がありますが,
&hai[0]は上からポインタです。


ちなみに,
hai : int [5][10]型 / int (*)[10]型になる→&hai[0]
&hai : int (*)[5][10]型
hai[0] : int [10]型 / int *型になる→&hai[0][0]
&hai[0] : int (*)[10]型
hai[0][0] : int型
&hai[0][0] : int *型
です。

たいちう 2002/04/24(水) 13:22:58
ちなみに、mallocで2次元の配列を確保すると、

int** hai = (int**)malloc(sizeof(int*)*M);
for (i=0; i<M; i++) {
    hai[i] = (int*)malloc(sizeof(int)*N);
}

こうなります。あるいは、

hai = (int**)malloc(sizeof(int*) * M + sizeof(int) * M * N);
for (i=0;i<M;i++)
    hai[i] = (int*)((char*)hai + sizeof(int*) * M + sizeof(int) * N * i);


これで、hai、hai[i]、hai[i][j]の関係がわかりやすくなるでしょうか?
ならないかな?

Puppy 2002/04/24(水) 14:00:14
YuOさん たいちうさんさん お返事ありがとうございます。
むずかしいので、じっくり読んで勉強したいと思います。

皆さん、頭こんがらがりませんかぁ?(笑)
Puppyはプログラマーに向いてないのかなぁ。(T-T;

>hai

あ、arrayでしたぁ。恥ずかしいですぅ。(*'-'*)

未熟者ですが、これからもヨロシクお願いします。 m(..)mぺこり

YuO [E-Mail] 2002/04/24(水) 17:39:02
>これで、hai、hai[i]、hai[i][j]の関係がわかりやすくなるでしょうか?

う〜ん,int hai[5][10]とint **haiは違うのですよ……。

int hai[5][10];
をmallocで真似ると,
typedef int T1[10];
T1 * p = malloc(sizeof(T1) * 5);
です。


int array[5][10];
と
int **pointer;
について,

array : int [5][10]型 / &array : int (*)[5][10]型 / (int)(&array + 1) -
(int)(&array) : sizeof(int) * 50
pointer : int **型 / &pointer : int ***型 / (int)(&pointer + 1) -
(int)(&pointer) : sizeof(int **)

array[0] : int [10]型 / &array[0] : int (*)[10]型 / (int)(&array[0] + 1) -
(int)(&array[0]) : sizeof(int) * 10
pointer[0] : int *型 / &pointer[0] : int **型 / (int)(&pointer[0] + 1) -
(int)(&pointer[0]) : sizeof(int *)

array[0][0] : int型 / &array[0][0] : int *型 / (int)(&array[0][0] + 1) -
(int)(&array[0][0]) : sizeof(int)
pointer[0][0] : int型 / &pointer[0][0] : int *型 / (int)(&pointer[0][0] + 1) -
(int)(&pointer[0][0]) : sizeof(int)

です。


int array[2][3]のメモリイメージは,
+-----------------------------------------------------------------------+
|                 array                 |
+-----------------------------------+-----------------------------------+
|       array[0]       |      array[1]        |
+-----------+-----------+-----------+-----------+-----------+-----------+
|array[0][0]|array[0][1]|array[0][2]|array[1][0]|array[1][1]|array[1][2]|
+-----------+-----------+-----------+-----------+-----------+-----------+
ですが,int **pointer;に2x3の疑似配列を確保した場合,
pointer
↓
+----------+----------+
|pointer[0]|pointer[1]|
+----------+----------+

pointer[0]
↓
+-------------+-------------+-------------+
|pointer[0][0]|pointer[0][1]|pointer[0][2]|
+-------------+-------------+-------------+

pointer[1]
↓
+-------------+-------------+-------------+
|pointer[1][0]|pointer[1][1]|pointer[1][2]|
+-------------+-------------+-------------+

ちなみに,int (*ptr_array)[3]に2x3の疑似配列を確保した場合
ptr_array
↓
+-----------------------------------------------+-----------------------------------------------+
|         ptr_array[0]         |        ptr_array[1]  
        |
+---------------+---------------+---------------+---------------+---------------+---------------+
|ptr_array[0][0]|ptr_array[0][1]|ptr_array[0][2]|ptr_array[1][0]|ptr_array[1][1]|ptr_array[1][2]|
+---------------+---------------+---------------+---------------+---------------+---------------+
になります。
#図の部分は等幅フォントで見て下さい。


>皆さん、頭こんがらがりませんかぁ?(笑)

慣れました(爆)。
とりあえず,上の図で,arrayとptr_arrayが同じように扱える為に,(b)があります。

arrayとpointerはメモリの配置が全く違いますので,同じように扱うことは出来ません。
が,arrayとptr_arrayは,arrayが配列全体を示すのに対して,ptr_arrayが先頭要素への
ポインタである以外,
同じメモリ配置ですから,同じように扱うことが出来ます。

で,通常arrayをptr_arrayに変換して使います。
#これはコンパイラが勝手に行います。


一次元だと,int var[5]がint *に変換されるので,ポインタと配列がごっちゃになって
覚えているのかもしれませんね。

YuO [E-Mail] 2002/04/24(水) 17:43:39
うりょ?ptr_arrayが崩れていますね……。
よし,ブラウザかえて再送信。

ptr_array
↓
+-----------------------------------------------+-------------------------------
----------------+
|         ptr_array[0]         |        ptr_array[1]    
      |
+---------------+---------------+---------------+---------------+---------------
+---------------+
|ptr_array[0][0]|ptr_array[0][1]|ptr_array[0][2]|ptr_array[1][0]|ptr_array[1][1]
|ptr_array[1][2]|
+---------------+---------------+---------------+---------------+---------------
+---------------+

YuO [E-Mail] 2002/04/24(水) 17:46:23
ダメでした……。
#Netscape 6.2.2とIE 6.00.2600.0000……
エディタにコピーして,改行を削除して見て下さい。

たいちう 2002/04/25(木) 10:01:35
>皆さん、頭こんがらがりませんかぁ?(笑)

こんがらがりました。まだ慣れてません。

int array[5][10]; と int **pointer;の違いについては、
一応理解していたつもりでした。
不注意で関数の引数に入れたりして、
コンパイラに文句を言われてましたから。
でも理解は曖昧だったようで、
mallocの例も不適切だったかもしれません。

YuOさんの説明((a)や(b)と書いてあるあたり)を再読して、
頭がすっきりしたと思いましたが、

> ちなみに,int (*ptr_array)[3]に2x3の疑似配列を確保した場合

について、こんがらがりました。例えば、
ptr_array[0][0] = 5
等のように使うための宣言(と確保?)のやり方が分かりません。

とりあえず、int (*ptr_array)[3]は、
「int [3]型」へのポインタでいいんですよね?

YuO [E-Mail] 2002/04/25(木) 17:55:50
>ptr_array[0][0] = 5
>等のように使うための宣言(と確保?)のやり方が分かりません。


二次元配列を扱う場合,二種類の方法があります。

(i)
int (*ptr_array)[3]; /* 宣言 */
ptr_array = malloc((sizeof(int) * 3) * 2); /* 確保 */

これは,int array[2][3];と同等のことを,動的に確保した場合です。
最高次以外の配列の数が,コンパイル時に既知である必要があります。

(ii)
int i;
int **ptr_array; /* 宣言 */
/* 確保 */
ptr_array = malloc(sizeof(int *) * 2);
for (i = 0; i < 2; ++i) ptr_array[i] = malloc(sizeof(int) * 3);

これは,たいちうさんの書かれた方法で,
動的に2次元配列を作成するときに一般的に使われている方法です。

コンパイル時に配列の数が決定している必要はありません。
ptr_array[0]とptr_array[1]で含まれる配列の数を変えることもできます。


関数は,単一の方法で両方を受け取ることはできません。

void func (int array[][3]);
または
void func (int (*array)[3]);
であれば,(i)またはarray[2][3]を渡せます。

void func (int **array);
であれば,(ii)のみを渡せます。

関数内でのみ使うのであれば,使いやすい方を使えばいいですが,
他の関数に渡す場合には,その関数に合わせてメモリを確保する必要があります。


>とりあえず、int (*ptr_array)[3]は、
>「int [3]型」へのポインタでいいんですよね?

そうです。
*は[]より結合が弱いので,宣言に括弧が使われています。
int *ptr_array[3];とすると,3つのint *からなる配列ですよね。

たいちう 2002/04/25(木) 19:41:23
YuOさま
解説有り難うございます。
お手数おかけしました。

int (*ptr_array)[10]
ptr_array = malloc((sizeof(int) * 10) * 5); 

と、5×10の(疑似)配列を使う場合には、

ptr_array : int (*)[10]型 / &ptr_array : int (**)[10]型 /
(int)(&ptr_array + 1) - (int)(&ptr_array) : sizeof(int (*)[10])

ptr_array[0] : int [10]型 / &ptr_array[0] : int (*)[10]型 /
(int)(&ptr_array[0] + 1) - (int)(&ptr_array[0]) : sizeof(int) * 10

ptr_array[0][0] : int 型 / &ptr_array[0][0] : int *型 /
(int)(&ptr_array[0][0] + 1) - (int)(&ptr_array[0][0]) : sizeof(int)

となる。これで多分あってますよね?

かつて某掲示板でYuOさんが他の方に同じようなことを
説明してたことを思い出しましたが、ちゃんと理解できてなかったため、
正確には思い出せません。
あのサイト復活しないかな?って、ここに書くべき内容ではないですね。

古いスレッドには追加発言できません。