掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板2

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧

No.29734

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/07 12:45:41)


ArrayToPointerと申します。

お聞きしたいことは、
多次元の配列として定義されている変数を
関数の引数で、ポインタとして受けたいのです。

例えば:

int array[10];
void func( int* pointer );

この場合、func()にarrayを渡すと暗黙的にポインタへと変換されると思います。
ですが、多次元の場合:

int array[10][10];
void func( int** pointer );

この場合は、func()にarrayを渡すと、[][10]を**に変換できないとエラーになります。
その他、次元が増えても同じです。

ですが、関数の引数を
void func( int pointer[10][10] );
とするのは、とてもきもち悪いように思えます。
(なにより、固定になるのが・・・)

みなさんはこのような場合、どのように対処されているのでしょうか?
よろしくお願いします。


この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:関数の引数で多次元配列を**で受ける 29735 sticky-bit 2007/02/07 13:24:05
<子記事> Re:関数の引数で多次元配列を**で受ける 29737 chu- 2007/02/07 15:14:49
<子記事> Re:関数の引数で多次元配列を**で受ける 29740 たかぎ 2007/02/07 16:51:33
<子記事> Re:関数の引数で多次元配列を**で受ける 29746 円零 2007/02/08 19:12:30


No.29735

Re:関数の引数で多次元配列を**で受ける
投稿者---sticky-bit(2007/02/07 13:24:05)


sticky-bitは、例えばこんな風にしています。
func関数の第1引数は、
「要素数が5である配列へのポインタ」です。
これを、擬似的に2次元配列とみなすと、
「行数はいくつかわからないが、列数は5である」ような
配列を扱うことができます。
func関数が直接知ることのできない行数の値は、
引数として渡します。

こちらには他に優秀な方が大勢いらっしゃいます。
もっとスッキリした方法を提示してくださることでしょう。

#include <stdio.h>

void func(int (*arr)[5], int n);

int main (void)
{
    int array1[4][5];
    int array2[3][5];
    int i, j;
    
    for (i = 0; i < 4; i++)
        for (j = 0; j < 5; j++)
            array1[i][j] = i * 100 + j;
    func(array1, 4);
    putchar('\n');
    
    for (i = 0; i < 3; i++)
        for (j = 0; j < 5; j++)
            array2[i][j] = i * 10000 + j;
    func(array2, 3);
    return 0;
}

void func(int (*arr)[5], int n)
{
    int i, j;
    
    for (i = 0; i < n; i++)
        for (j = 0; j < 5; j++)
            printf("arr[%d][%d]=%d\n", i, j, arr[i][j]);
}




この投稿にコメントする

削除パスワード

No.29738

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/07 16:10:16)


回答ありがとうございます。

>void func(int (*arr)[5], int n);
これでは、*[5]の型にとらわれてしまうので避けたいところだったりします。


この投稿にコメントする

削除パスワード

No.29737

Re:関数の引数で多次元配列を**で受ける
投稿者---chu-(2007/02/07 15:14:49)


2次元配列を関数で受け取るパターンをいくつか書き出してみました。
func1()からfunc4()へいくにしたがって自由度を増やしています。

恐らく状況によって使い分けることになるのではないでしょうか。
私の場合、列数が決まっている2次元配列を使う状況が多いので、
下記func2()の形を用いることが多いです。

[test.c]
#include <stdio.h>

#define ARRAYSIZE(array) (sizeof(array)/sizeof((array)[0]))

void func1(int (*pointer)[2][3])
{
    int i, j;
    printf("func1:");
    for ( j = 0; j < ARRAYSIZE(*pointer); j++ ) {
        for ( i = 0; i < ARRAYSIZE((*pointer)[0]); i++ ) {
            printf(" %d", (*pointer)[j][i]);
        }
    }
    printf("\n");
}

void func2(int (*pointer)[3], int y) /* yを自由に */
{
    int i, j;
    printf("func2:");
    for ( j = 0; j < y; j++ ) {
        for ( i = 0; i < ARRAYSIZE(*pointer); i++ ) {
            printf(" %d", pointer[j][i]);
        }
    }
    printf("\n");
}

void func3(void *pointer, int x, int y) /* xを自由に */
{
    int i, j;
    printf("func3:");
    for ( j = 0; j < y; j++ ) {
        for ( i = 0; i < x; i++ ) {
            printf(" %d", *((int *)pointer + x * j + i));
        }
    }
    printf("\n");
}

void func4(void *pointer, int size, int x, int y) /* 型を自由に */
{
    int i, j, s;
    printf("func4:");
    for ( j = 0; j < y; j++ ) {
        for ( i = 0; i < x; i++ ) {
            printf(" ");
            for ( s = 0; s < size; s++ ) {
                printf("%02X", *((unsigned char *)pointer + (x * j + i) * size + s));
            }
        }
    }
    printf("\n");
}

int main(void)
{
    int array[][3] = {
        { 1, 2, 3, },
        { 4, 5, 6, },
    };

    func1(&array);

    func2(array, 2);

    func3(array, 3, 2);

    func4(array, sizeof(int), 3, 2);

    return 0;
}

[出力] 環境:LSI-C
>lcc test

>test
func1: 1 2 3 4 5 6
func2: 1 2 3 4 5 6
func3: 1 2 3 4 5 6
func4: 0100 0200 0300 0400 0500 0600

>



この投稿にコメントする

削除パスワード

No.29739

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/07 16:14:24)


回答ありがとうございます。

>void func1(int (*pointer)[2][3])
型にとらわれたくないので、これは使用したくないです。

>void func3(void *pointer, int x, int y) /* xを自由に */
また、これだと要素を参照するときがめんどくさくなり、
多次元の配列として扱っていた利点がなくなってしまうように感じます。

ただ、私が望んでいるような動作が実質不可能であるならば、
最善策はこれだと思います。

C++ならいいのだけれど。。。


この投稿にコメントする

削除パスワード

No.29741

Re:関数の引数で多次元配列を**で受ける
投稿者---chu-(2007/02/07 17:15:01)


>>void func3(void *pointer, int x, int y) /* xを自由に */
>また、これだと要素を参照するときがめんどくさくなり、
>多次元の配列として扱っていた利点がなくなってしまうように感じます。

参照をスマートに行いたいのであれば下記func5()はどうでしょうか。
ただし、動的な多次元配列の必要があるため、前処理と後処理が必要です。

以下も参照してください。

> Re:動的に確保されたメモリへのアクセス
> http://www3.realint.com/cgi-bin/tarticles.cgi?pointc2+7034

#include <stdio.h>
#include <stdlib.h>

#define ARRAYSIZE(array) (sizeof(array)/sizeof((array)[0]))

void func5(int **pointer, int x, int y)
{
    int i, j;
    printf("func5:");
    for ( j = 0; j < y; j++ ) {
        for ( i = 0; i < x; i++ ) {
            printf(" %d", pointer[j][i]);   /* 参照がスマート */
        }
    }
    printf("\n");
}

int main(void)
{
    int x = 3;
    int y = 2;
    int **a;
    int *p;
    int i;

    a = malloc(sizeof(int *) * y + sizeof(int) * y * x);
    if (!a) return 1;
    for (p = (int *)(a + y), i = 0; i < y; i++, p += x) a[i] = p;

    a[0][0] = 1;    a[0][1] = 2;    a[0][2] = 3;
    a[1][0] = 4;    a[1][1] = 5;    a[1][2] = 6;

    func5(a, 3, 2);

    free(a);

    return 0;
}



この投稿にコメントする

削除パスワード

No.29742

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/07 19:45:02)


回答ありがとうございます。

> a = malloc(sizeof(int *) * y + sizeof(int) * y * x);
これも元は配列で持ちたいのです。

なので、申し訳ないのですが、私の望むものと異なってしまいます。


この投稿にコメントする

削除パスワード

No.29747

Re:関数の引数で多次元配列を**で受ける
投稿者---chu-(2007/02/09 10:52:37)


動的な多次元配列である必要はありませんでした。
簡単にしたmain関数を2種類添付します。

int main(void)
{
    int array[][3] = {
        { 1, 2, 3, },
        { 4, 5, 6, },
    };
    int *ptr[ARRAYSIZE(array)];
    int i;

    for ( i = 0; i < ARRAYSIZE(array); i++ ) ptr[i] = array[i]; /* 前処理 */

    func5(ptr, 3, 2);

    return 0;
}

int main(void)
{
    static int array[][3] = {   /* C89ではstaticが必要 */
        { 1, 2, 3, },
        { 4, 5, 6, },
    };
    int *ptr[] = {
        array[0],
        array[1],
    };

    func5(ptr, 3, 2);

    return 0;
}



この投稿にコメントする

削除パスワード

No.29750

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/09 12:49:32)


回答ありがとうございます。

int array[][3] = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
int *ptr[ARRAYSIZE(array)];

提供して下さった両方とも、
私が望んでいたイメージに一番近いものとなります。

これでいこうと思います。
皆さんありがとうございました。


この投稿にコメントする

削除パスワード

No.29740

Re:関数の引数で多次元配列を**で受ける
投稿者---たかぎ(2007/02/07 16:51:33)
http://takagi.in/


環境が書かれていないので、正確なことは言えませんが...

>int array[10][10];
>void func( int** pointer );
>
>この場合は、func()にarrayを渡すと、[][10]を**に変換できないとエラーになります。

警告が出ることはあってもエラーにはならないと思います。
C++でしょうか?

>ですが、関数の引数を
>void func( int pointer[10][10] );
>とするのは、とてもきもち悪いように思えます。
>(なにより、固定になるのが・・・)

だとすれば、
void func(size_t m, size_t n, int array[*][*]);
とするしかないと思います。
関数本体では、
void func(size_t m, size_t n, int array[m][n])
{
  ...
}
とします。
C++なら
template <typename T, std::size_t M, std::size_t N>
void func(T (&a)[M][N]);
とすればよいでしょう。



この投稿にコメントする

削除パスワード

No.29743

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/07 19:48:14)


回答ありがとうございます。

>環境が書かれていないので、正確なことは言えませんが...
申し訳ないです。
環境は、VC6 & VC8@Express にて確認しております。

>警告が出ることはあってもエラーにはならないと思います。
エラーになってしまいましたorz

void func(size_t m, size_t n, int array[*][*]);
</pre>とするしかないと思います。
>関数本体では、
><pre>
void func(size_t m, size_t n, int array[m][n])
{
...
}
</pre>とします。
これは思いつかなかったです。
でも、微妙な感じもまた。。。

>C++なら
><pre>
template <typename T, std::size_t M, std::size_t N>
void func(T (&a)[M][N]);
</pre>とすればよいでしょう。
もしくはvector使ったり。。。


この投稿にコメントする

削除パスワード

No.29746

Re:関数の引数で多次元配列を**で受ける
投稿者---円零(2007/02/08 19:12:30)


単に受けるだけなら
func((int**)array);
でいいじゃないですか。
受け取った先で多次元配列に戻せませんけど。

て言うかNo.29739で「私が望んでいるような動作」と言いながら、
具体的にどんな動作を望んでいるのか一言も言ってないような気がするんですが。

たかぎさんの回答の可変長配列に対しても、
「微妙な感じ」って何が不満なのかさっぱりわからないし。


この投稿にコメントする

削除パスワード

No.29748

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/09 12:42:48)


回答ありがとうございます。

>単に受けるだけなら
>func((int**)array);
>でいいじゃないですか。
>受け取った先で多次元配列に戻せませんけど。
配列宣言されたarrayをfunc()に対して**にキャストして渡すってことでしょうか?
VC8ExpressでやるとC2440エラーになってしまいました。
エラーではint**にキャストされたarray[][]がint型と見なされた。。。
なんでだろう?

>て言うかNo.29739で「私が望んでいるような動作」と言いながら、
>具体的にどんな動作を望んでいるのか一言も言ってないような気がするんですが。
「渡す側は配列でデータを持ちたい。
けれども、受け取る側の関数では引数として、渡す側の配列の次元と同じ数のポインタで受け取りたい。」が望むものとなります。

>たかぎさんの回答の可変長配列に対しても、
>「微妙な感じ」って何が不満なのかさっぱりわからないし。
確かに。申し訳ないです。

なにが微妙なのかというと、
皆さんの指摘に対して私が返しているように、
受け取る側の関数で、例えば
int fun( int p[][5] )
のように、固定値としたくないというものがあります。

ですが、たかぎさんの回答では、それらのサイズを固定とはせず、
別引数と受け取ることによって改善されております。
これにより、「固定値としたくない」というものに関しては
クリアされています。

ですが、上記で述べた、
「渡す側の配列の次元と同じ数のポインタで受け取る」
というものと一致しなかったため、
私が望むものの半分は満たされたが、残り半分が満たされなかったため、
"微妙"という表現をしてしまいました。

たしかに、これらに対する説明を入れていなかったことは、
たかぎさんに不快感を与えてしまったと思います。

申し訳ありません。


この投稿にコメントする

削除パスワード

No.29749

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/09 12:49:08)


>VC8ExpressでやるとC2440エラーになってしまいました。
>エラーではint**にキャストされたarray[][]がint型と見なされた。。。
>なんでだろう?
すいません。うそつきましたorz
問題なくコードは通ります。

申し訳ありませんでした。


この投稿にコメントする

削除パスワード

No.29751

Re:関数の引数で多次元配列を**で受ける
投稿者---たかぎ(2007/02/09 15:29:08)
http://takagi.in/


>>VC8ExpressでやるとC2440エラーになってしまいました。
>>エラーではint**にキャストされたarray[][]がint型と見なされた。。。
>>なんでだろう?
>すいません。うそつきましたorz
>問題なくコードは通ります。

キャストしなくても通るはずですよ。
/WXオプションを付けて、警告をエラーにするようにしていれば話は別ですが...

int[M][N]とint**では型に互換性がありませんから、静的な型を無視して関数に渡すことができても、関数内で元の型にキャストし直すしかありません。
それならいっそのことvoid*で受けた方がましです。



この投稿にコメントする

削除パスワード

No.29755

Re:関数の引数で多次元配列を**で受ける
投稿者---yoh2(2007/02/10 00:56:45)


> int[M][N]とint**では型に互換性がありませんから

ここをしっかり抑えておくことが重要ですね。
配列は、式中で基本的に自身の最初の要素を指すポインタとして扱われますが、これ、
配列の配列に対して再帰的に適用できるわけではありません。そのように扱われたら
計算結果が異なってしまいます。

というのは、int a[M][N]と宣言された配列に対してa[i][j]と書いた場合と、
int **ppと宣言されたポインタに対してpp[i][j]と書いたものでは、一見同じことを
やっているように見えますが、その実かなり内容が異なるからです。

pp[i][j]と書いたものは単純で、

   式       : 型     : 型の意味
A) pp       :  int** : (intを指すポインタ)を指すポインタ
   に、[i]が適用されて
B) pp[i]    : int *  : intを指すポインタ
   さらに[j]が適用されて
C) pp[i][j] : int    : int

となります。

一方、a[i][j]と書いた場合は結構複雑で、
   式      : 型        : 型の意味
a) a       : int[M][N] : (intがN個からなる配列)がM個からなる配列全体
   ここで、配列が、その最初の要素を指すポインタに変換されて、
b) a       : int(*)[N] : (intがN個からなる配列)を指すポインタ
   これに[i]が適用されて
c) a[i]    : int[N]    : intがN個からなる配列全体
   さらに配列がその最初の要素を指すポインタに変換されて
d) a[i]    : int*      : intを指すポインタ
   最後に[j]が適用されて
e) a[i][j] : int       : int

となります。

ちなみに、関数の引数としてint[N][M]が宣言された場合は、int (*)[M]として宣言した
ものとして扱う特殊ルール(関数の引数として配列が宣言された場合はポインタとして扱う;
再帰的には適用されない)があるので、(b)〜(e)の演算が行われます。

つまり、演算子の表記自体は対応している、p[i][j]の(A)〜(B)とa[i][j]の(a)〜(d)では、
全く違うことを行っているわけです。
そのため、int[M][N]がint**として扱えた日には、(B)で、(int[N]からなる配列)の配列の
i番目の要素から始まる内容(intそのものが入っています)をポインタとして解釈してしまい、
あらぬ彼方を指してしまう羽目になります。
型に互換性がない、というのはこういった理由によります。

これ、メモリ配置の絵を書き、それぞれの式が何を指すか書き込みながら考えると分かり易い
のですが、AAの才能がないので例示できません。申し訳ない。


この投稿にコメントする

削除パスワード

No.29756

Re:関数の引数で多次元配列を**で受ける
投稿者---yoh2(2007/02/10 01:03:17)


途中にコメントを入れたら文脈の繋がりがぶち切れた……

>つまり、演算子の表記自体は対応している、p[i][j]の(A)〜(B)とa[i][j]の(a)〜(d)では、
>全く違うことを行っているわけです。

この「つまり」は、ふたつ前の段落の

> 一方、a[i][j]と書いた場合は結構複雑で、
……
> となります。

にかかります。
その間の

> ちなみに、関数の引数としてint[N][M]が宣言された場合は、……

にかけてしまうと意味が通じなくなります。すみません。


この投稿にコメントする

削除パスワード

No.29766

関数の引数で多次元配列を**で受ける
投稿者---ArrayToPointer(2007/02/13 12:21:09)


回答ありがとうございます。

根本的な原因を明確にして頂き、理解が深まります。

それぞれに関する内部の動きをしっかりと把握していきたいと思います。

ありがとうございました。


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧