web-dev-qa-db-ja.com

多次元可変長配列を関数に渡す

似たような質問がたくさんありますが、それでもC99/C11の可変長配列の機能に関連する答えは見つかりませんでした。

多次元可変長配列をC99/C11の関数に渡す方法は?

例えば:

_void foo(int n, int arr[][]) // <-- error here, how to fix?
{
}

void bar(int n)
{
    int arr[n][n];
    foo(n, arr);
}
_

コンパイラ(_g++-4.7 -std=gnu++11_)言います:
_error: declaration of ‘arr’ as multidimensional array must have bounds for all dimensions except the first_

それを_int *arr[]_に変更しても、コンパイラーはまだ文句を言います:
error: cannot convert ‘int (*)[(((sizetype)(((ssizetype)n) + -1)) + 1)]’ to ‘int**’ for argument ‘2’ to ‘void foo(int, int**)’

次の質問、値で渡す方法と参照で渡す方法は?どうやら、通常、配列を関数に渡すときに配列全体をコピーすることは望ましくありません。

定数の配列では、「定数」が示すように、関数を宣言するときに長さを知っている必要があるため、単純です。

_void foo2(int n, int arr[][10]) // <-- ok
{
}

void bar2()
{
    int arr[10][10];
    foo2(10, arr);
}
_

このような関数に配列を渡すことはベストプラクティスではなく、まったく気に入らないことは承知しています。おそらく、フラットポインター、オブジェクト(std:vectorなど)、またはその他の何らかの方法を使用した方がよいでしょう。しかし、それでも、私は理論的な観点からここでの答えは何なのか好奇心が強いです。

35
alveko

CとC++では、関数に配列を渡すのは少しおかしいです。配列型の右辺値はないため、実際にはポインターを渡します。

2D配列(配列の配列ではなく、実際の配列)をアドレスするには、2つのチャンクのデータを渡す必要があります。

  • 開始位置へのポインタ
  • 1行の幅

そしてこれらは、C、C++、VLAあり、なし、またはその他の2つの別個の値です。

それを書くいくつかの方法:

最もシンプルで、どこでも動作しますが、より多くの手作業が必要です

void foo(int width, int* arr) {
    arr[x + y*width] = 5;
}

VLA、標準C99

void foo(int width, int arr[][width]) {
    arr[x][y] = 5;
}

VLA w /逆引き引数、フォワードパラメーター宣言(GNU C拡張)

void foo(int width; int arr[][width], int width) {
    arr[x][y]=5;
}

C++ w/VLA(GNU C++拡張、ひどくい)

void foo(int width, int* ptr) {
    typedef int arrtype[][width];
    arrtype& arr = *reinterpret_cast<arrtype*>(ptr);
    arr[x][y]=5;
}

大きな発言:

2D配列の[x] [y]表記は、配列の型に幅が含まれているため機能します。 VLAなし=配列型はコンパイル時に修正する必要があります。

したがって:VLAを使用できない場合は...

  • cで処理する方法はありません。
  • c ++でオーバーロードされた演算子のオーバーロードを伴うプロキシクラスなしで処理する方法はありません。

VLA(C99またはGNU C++拡張))を使用できる場合、...

  • あなたはCの緑にいます、
  • c ++で混乱が必要な場合は、代わりにクラスを使用してください。

C++の場合、boost::multi_arrayは確実な選択です。

回避策

2D配列の場合、2つの個別の割り当てを行うことができます。

  • T(A)へのポインターの1D配列
  • Tの2D配列(B)

次に、(A)のポインターを(B)の各行を指すように設定します。

このセットアップでは、(A)を単純なT**そして[x][y]インデックス作成。

このソリューションは2Dには適していますが、高次元にはますます多くの定型文が必要です。また、追加の間接層があるため、VLAソリューションよりも低速です。

また、Bの行ごとに個別の割り当てを行う同様のソリューションに遭遇する場合があります。Cでは、これはmalloc-in-a-loopのように見え、C++のvector-of-vectorsに類似しています。 。ただし、これにより、配列全体を1つのブロックにする利点がなくなります。

43
Kos

これを行う明確な方法はありませんが、回避策を使用して2次元配列を1次元配列として扱い、関数内で2次元配列に再変換できます。

void foo2(int n, int *arr) 
{
    int *ptr; // use this as a marker to go to next block
    int i;
    int j;

    for(i = 0; i < n; i++)
    {
        ptr = arr + i*n; // this is the starting for arr[i] ...
        for (j = 0; j < n ;j++)
        {
            printf(" %d ", ptr[j]); // This is same as arr[i][j]
        }
    }
}

void bar2()
{
    int arr[10][10];
    foo2(10, (int *)arr);
}
1
Pankaj Rai