ページ 11

二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 00:22
by reghorn
二次元配列を引数として受け取りその中身を表示する関数testを作ろうとしているのですが
コンパイルは通るのですが実行すると不正なアドレスを読み込んでいるようで動いてくれません。
具体的なエラーメッセージは以下のとおりで、停止時にVisualStudioのデバッガの矢印(?)はprintfの行で止まっています。

test.exe の 0x008213e8 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0x00000000 を読み込み中にアクセス違反が発生しました。

1次元配列を受け取り先頭要素を印字するtest2は動いてくれるので同じ方法でできると思ったのですがどうも違うようです。
二次元配列の場合はどのようにすればいいのでしょうか?
testの引数を test(double a[2][2]) などと具体的に指定してやれば動くのはわかりますが今回はサイズの分からない二次元配列に使えるような関数を作りたいのでこの方法は取れません。

実行環境
windows7 Pro 64bit
Visual Studio 2008

C言語の理解度
入門書を一通りやった程度でその内容を100%熟知してはいません
#include<stdio.h>
void test(double** a){
    printf("%f\n",a[0][0]);
}
void test2(double* a){
    printf("%f\n",a[0]=5);
}

int main(void){
    int i,j;
    double a[2][2]={{1,2},{3,4}};
    double x[2]={1,2};
    for(i=0;i<2;i++)
        for(j=0;j<2;j++)
            printf("%f\n",a[j]);    //ここまで動く

    //test(a); //呼びだすとtestのprintfを実行時にエラー
    test2(x);    //これも動く

    return 0;
}


画像

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 00:45
by やっくん
void test(double a[/url][2]){
printf("%f\n",a[0][0]);
}

このように記述すれば一応動きます。

double** a に a[2][2]
を渡そうとすると何故エラーかというと、これはポインタのポインタ変数に対して、ただの配列のアドレスを渡していることになりますのでエラーとなります。

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 00:53
by reghorn
あう、編集中に回答をしていただいたようです申し訳ない
恐らくやっくんさんが回答を書いている間に質問文を編集して次の文を追加しました。
”testの引数をtest(double a[2][2]) などと具体的に指定してやれば動くのはわかりますが今回はサイズの分からない二次元配列に使えるような関数を作りたいのでこの方法は取れません。”

>>double** a に a[2] [2]
を渡そうとすると何故エラーかというと、これはポインタのポインタ変数に対して、ただの配列のアドレスを渡していることになりますのでエラーとなります。

1次元の配列の場合もポインタ変数に対して配列のアドレスを渡して動いていますがポインタのポインタの場合だと駄目なんでしょうか?
画像

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 00:55
by やっくん
<追記>
ポインタを使うのであれば、以下のような書き方もできます。
これが正しいかわかりませんので参考程度にお願いします(^^;
void test(double* a, int n1, int n2)
{
  int i, j;
  for(i = 0; i < n1; i++)
    for(j = 0; j < n2; j++)
      printf("[%d][%d] = %f\n", i, j, a[i*n2 + j]);
}

// main側で呼び出す場合は
test(a, 2, 2);
配列はまとまってメモリを取ったものに過ぎないので、2次元、3次元、・・・、N次元としたところで1次元で表現できます。

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 01:13
by reghorn
メモリ上の二次元配列は
a[0][0]
a[0][1]
a[1][0]
a[1][1]
となっているはず(環境による?)なので仰る通り1次元で表現できるのは理解できます。
aの要素を全部出すにはaのサイズを渡さないといけないのもわかりますが今回は先頭要素だけ見れればいいので割愛しています。
デバッガで追ってみるとmain内部での*aはa[0][0]でしたがtestに渡すと0x00000000になるみたいです。
うーん、もうちょっと本とか見てみます・・・。

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 01:22
by やっくん
質問の意図を理解できていなかったらすみません(^^;
私もこの辺りが整理できたのが最近なのであまり良い回答をできないかもしれませんが。

>1次元の配列の場合もポインタ変数に対して配列のアドレスを渡して動いていますがポインタのポインタの場合だと駄目なんでしょうか?

これは
int a[2][2];
とした場合はポインタのポインタに渡せる。
つまり
int ** ⇔ int [/url][/url]
と解釈されているのでしょうか?

int a[2][2];
は先程書いたようにメモリ上では1次元であり、そのアドレスはポインタのポインタでは無く、ポインタで指します。
「ポインタのポインタ」は「ポインタ」を指すものであるので、ここでの2次元配列配列のアドレスを指すことができません。

もし、関数内で2次元配列として扱いたいのであれば、
main側の変数を
int **a;
と宣言し、任意のメモリを動的確保し、関数に受け渡せば2次元配列として振まうことができますよ。 画像

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 01:32
by reghorn
>>int ** ⇔ int [/url][/url]
と解釈されているのでしょうか?

エスパーしていただいて助かってます、その通りでした。
手持ちの本で配列とポインタは等価に使えると書いてあったのをなんだか勘違いして理解していたようです。

>>
もし、関数内で2次元配列として扱いたいのであれば、
main側の変数を
int **a;
と宣言し、任意のメモリを動的確保し、関数に受け渡せば2次元配列として振まうことができますよ。

やりたいことはおっしゃている通りです。助かりました、ありがとございます。

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 01:46
by やっくん
参考になるサイトを探してみました。
やり方次第で動的確保せずにすっきりできるみたいです(^^;
下記のサイトを下に読み進めていけば手法が書いてあります。

http://c-production.com/contents/c/sec10.html#09

Re:二次元配列を引数として渡す方法

Posted: 2010年7月14日(水) 01:52
by reghorn
ありがとうございます、今日はもう時間が遅いので明日以降読んでみます