Cで3次元配列をMallocしますか?

arrays c multidimensional-array
Cで3次元配列をMallocしますか?

一部のMATLABコードをCに変換していますが、変換しているスクリプトは、10 * 100 * 300の複雑なエントリを持つ3D配列を多用しています。 配列のサイズもセンサーの入力に依存します。理想的には、配列を動的に割り当てる必要があります。 これまでのところ、2つのアプローチを試しました。最初のアプローチは、

value = array[x + (y*xSize) + (z*ySize*xSize)]

これは私の脳を傷つけます。 ポインターの配列の配列も試しました

int main () {
  int ***array = malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}

データを割り当てようとすると、セグエラーが発生します。

完璧な世界では、より簡潔で簡単なプログラミングのために、2番目の方法と配列表記を使用したいと思います。 Cで3次元配列を動的に割り当てるより良い方法はありますか?

  28  12


ベストアンサー

潜在的に数千の断片化されたメモリブロックではなく、再生するメモリの単一ブロックを提供するため、最初のオプション(単一の1D配列)を選択します

ただし、配列の正しい要素にアクセスして頭を動かしている場合は、x、y、zの位置を1D配列へのオフセットに変換するユーティリティメソッドを記述します

int offset(int x, int y, int z) {
    return (z * xSize * ySize) + (y * xSize) + x;
}

18


他の人が言ったように、1つの連続したメモリチャンクを割り当ててから、自分でインデックスを作成することをお勧めします。 必要に応じて関数を作成できます。 しかし、複数の `malloc()`ケースを処理する方法を知りたいので、ここに例を示します:

最初に、関数 free_data()`を定義します。この関数は、最初の2つの次元サイズとして `xlen`と ylen`を持つ `int *`を解放します。 `free()`が解放されるポインタの長さをとらないように、 `zlen`パラメータは必要ありません。

void free_data(int ***data, size_t xlen, size_t ylen)
{
    size_t i, j;

    for (i=0; i < xlen; ++i) {
        if (data[i] != NULL) {
            for (j=0; j < ylen; ++j)
                free(data[i][j]);
            free(data[i]);
        }
    }
    free(data);
}

関数はポインター data`をループし、 i`番目の int `ポインター `data [i]`を見つけます。 次に、指定された `int`ポインターに対して、ループし、 `data [i] [j]`の `j`番目の int * `を見つけて解放します。 また、すべての `data [i] [j]`を解放したら、 `data [i]`を解放する必要があります。最後に、 `data`自体を解放する必要があります。

次に、割り当て機能について説明します。 関数はエラーチェックによって少し複雑です。 特に、 1 + xlen + xlen * ylen`の malloc`呼び出しがあるため、これらの呼び出しのいずれかで障害を処理し、これまでに割り当てたすべてのメモリを解放する必要があります。 物事を簡単にするために、 `free(NULL)`がno-opであるという事実に依存しているため、エラーが発生した場合にそれらを割り当てる前に、すべてのポインターを `NULL`に等しいレベルに設定します。 、すべてのポインターを解放できます。

それ以外は、関数は十分に単純です。 最初に xlen`の int ** 値にスペースを割り当て、次にそれらの xlen`ポインターのそれぞれに ylen`の int * 値にスペースを割り当て、次にそれらの xlen * ylen`ポインターのそれぞれにスペースを割り当てます、 zlen`の int`値にスペースを割り当て、 xlen * ylen * zlen`の int`値に合計スペースを与えます。

int ***alloc_data(size_t xlen, size_t ylen, size_t zlen)
{
    int ***p;
    size_t i, j;

    if ((p = malloc(xlen * sizeof *p)) == NULL) {
        perror("malloc 1");
        return NULL;
    }

    for (i=0; i < xlen; ++i)
        p[i] = NULL;

    for (i=0; i < xlen; ++i)
        if ((p[i] = malloc(ylen * sizeof *p[i])) == NULL) {
            perror("malloc 2");
            free_data(p, xlen, ylen);
            return NULL;
        }

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            p[i][j] = NULL;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            if ((p[i][j] = malloc(zlen * sizeof *p[i][j])) == NULL) {
                perror("malloc 3");
                free_data(p, xlen, ylen);
                return NULL;
            }

    return p;
}

私は malloc`呼び出しをかなり単純化したことに注意してください:一般に、 malloc`の戻り値をキャストして、タイプの代わりに `sizeof`演算子のオペランドとして割り当てたいオブジェクトを指定するべきではありません。 これにより、 `malloc`の呼び出しが記述しやすくなり、エラーが発生しにくくなります。 「malloc」には「stdlib.h」を含める必要があります。

上記の2つの機能を使用したテストプログラムを次に示します。

#include
#include
#include
#include

int main(void)
{
    int ***data;
    size_t xlen = 10;
    size_t ylen = 100;
    size_t zlen = 300;
    size_t i, j, k;

    srand((unsigned int)time(NULL));
    if ((data = alloc_data(xlen, ylen, zlen)) == NULL)
        return EXIT_FAILURE;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            for (k=0; k < zlen; ++k)
                data[i][j][k] = rand();

    printf("%d\n", data[1][2][1]);
    free_data(data, xlen, ylen);
    return EXIT_SUCCESS;
}

使いやすいと思う場合は、必ずこのアプローチを使用してください。 一般に、これはメモリの連続したチャンクを使用するよりも遅くなりますが、上記のスキームで速度が問題ない場合、そしてそれがあなたの生活を楽にするならば、あなたはそれを使い続けることができます。 使用しない場合でも、このようなスキームを機能させる方法を知っておくと便利です。

9


「malloc」を使用する必要がありますか? Cでは、多次元配列をネイティブに作成できます。

int a2[57][13][7];

または、次の方法で `malloc`を使用できます。

int (*a)[13][7]; // imitates 3d array with unset 3rd dimension
                 // actually it is a pointer to 2d arrays

a = malloc(57 * sizeof *a);    // allocates 57 rows

a[35][7][3] = 12; // accessing element is conventional

free(a); // freeing memory

8


Cの配列型はコンパイル時の既知の値でのみ指定できるため、C89には希望することを行う方法はありません。 そのため、狂った動的な割り当てを避けるためには、一次元の方法に固執する必要があります。 このプロセスを容易にするために関数を使用できます

int index(int x, int y, int z) {
  return x + (y*xSize) + (z*ySize*xSize);
}

int value = array[index(a, b, c)];

C99では、次元がランタイム値であっても、通常の配列構文を使用できます。

int (*array)[X][Y][Z] = (int(*)[X][Y][Z])malloc(sizeof *p);
// fill...
int value = (*array)[a][b][c];

ただし、ローカルの非静的配列でのみ機能します。

7


ああ、malloc配列の割り当てが嫌いですか^^

これは正しいバージョンです。基本的には1行の誤りです。

int main () {
  int ***array = (int***)malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    // Assign to array[i], not *array[i] (that would dereference an uninitialized pointer)
    array[i] = (int**)malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = (int*)malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}

6


この方法では、メモリのブロックを1つだけ割り当てることができ、動的配列は静的ブロックのように動作します(つまり、 同じメモリ連続性)。 通常の1次元配列のように、単一のfree(配列)でメモリを解放することもできます。

double*** arr3dAlloc(const int ind1, const int ind2, const int ind3)
{
  int i;
  int j;
  double*** array = (double***) malloc( (ind1 * sizeof(double*)) + (ind1*ind2 * sizeof(double**)) + (ind1*ind2*ind3 * sizeof(double)) );
  for(i = 0; i < ind1; ++i) {
    array[i] = (double**)(array + ind1) + i * ind2;
    for(j = 0; j < ind2; ++j) {
      array[i][j] = (double*)(array + ind1 + ind1*ind2) + i*ind2*ind3 + j*ind3;
    }
  }
  return array;
}

2


セグメンテーションについて、他の誰かがこれを指摘していると確信していますが、念のため、最初のforループの最初の行に余分な「*」があります

for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
//  ^ we dont want to deference array twice
    for (j = 0; j < 3; j++) {
        array[i][j] = malloc(3*sizeof(int));
    }
}

以下を試してください。

    for (i = 0; i < 3; i++) {
        array[i] = malloc(3*sizeof(int*));
        for (j = 0; j < 3; j++) {
            array[i][j] = malloc(3*sizeof(int));
        }
    }

2


これがあなたを助けることを願っています!!!!

3D配列内の2D配列にメモリを割り当てている間、割り当てられたメモリを* array [i]ではなくarray [i]に割り当てます。これは、セグエラーなしで機能します。

これがあなたのプログラムです

int main ()
{
    int ***array = malloc(3*sizeof(int**));
    int i, j;

    for (i = 0; i < 3; i++) {
       array[i] = malloc(3*sizeof(int*));
       for (j = 0; j < 3; j++) {
          array[i][j] = malloc(3*sizeof(int));
       }
    }

    array[1][2][1] = 10;

    return 0;

}

1


3Dメモリ割り当てのコードの下:

int row3d = 4;
int column3d = 4;
int height3d =4;
int val3d =10;

int ***arr3d = (int***)malloc (row3d*sizeof(int**));
for (int i =0 ; i

1


これを、3D配列を割り当てるための2つの根本的に異なる方法として認識させる必要があります。 この認識は、2つの明確な差別化の詳細によって強化されます。1)2番目の方法は、いくつかのレベルの_indirection_を使用して実際の要素にアクセスします。2)2番目の方法は、

しかし、なぜ下位レベルの1D配列を「独立して」割り当てることに厳密に固執するのですか? そうする必要はありません。 そして、それを考慮に入れると、3Dアレイを構築する3番目の方法があることに気付くはずです。

int ***array3d = malloc(3 * sizeof(int **));
int **array2d = malloc(3 * 3 * sizeof(int *));
int *array1d = malloc(3 * 3 * 3 * sizeof(int));

for (size_t i = 0; i < 3; i++)
{
  array3d[i] = array2d + i * 3;
  for (size_t j = 0; j < 3; j++)
    array3d[i][j] = array1d + i * 3 * 3 + j * 3;
}

array[1][2][1] = 10;

この割り当て方法をよく見ると、最終的にこれは2番目の方法とほぼ同じであることがわかります。間接レベルの各レベルで中間ポインターを使用して3レベルの配列構造を構築します。 唯一の違いは、複数の繰り返しmalloc呼び出しを行う代わりに、間接的に各レベルのメモリを事前に「ワンショットで」事前に割り当てることです。 後続のサイクルは、事前に割り当てられたメモリをサブアレイ間で単純に分配します(つまり、 ポインタを初期化するだけです)。

しかし、さらによく見ると、実際の配列要素のメモリ(実際の値を格納する int`s)は、最初のメソッドとまったく同じ方法で割り当てられていることに気付くでしょう: malloc( 3 * 3 * 3 * sizeof(int)); `-平らで連続した配列として。

さて、考えてみると、この3番目の方法は最初の方法とそれほど変わらないことを理解する必要があります。 両方とも、データを保存するためにサイズ `xSize * ySize * zSize`のフラット配列を使用します。 ここでの唯一の本当の違いは、そのフラットデータにアクセスするためのインデックスの計算に使用する方法です。 最初の方法では、オンザフライでインデックスを計算します

array1d[z * ySize * xSize + y * xSize + x]

3番目の方法では、基本的に同じ式を使用して、事前に配列要素へのポインターを事前に計算し、事前に計算した結果を追加の配列に格納し、後で「自然な」配列アクセス構文を使用して取得します

array3d[x][y][x]

ここでの質問は、この事前計算が余分な労力と余分なメモリの価値があるかどうかです。 答えは次のとおりです。通常、いいえ、そうではありません。 この余分なメモリを費やすことで、パフォーマンス上のメリットを享受することはできません(コードが遅くなる可能性があります)。

2番目の方法を検討する価値がある唯一の状況は、真に_jagged / ragged_配列を処理している場合です。サブ配列の一部が欠落/未使用であるか、サイズが小さくなっているスパース多次元配列です。 たとえば、3D配列の一部の1Dまたは2Dサブ配列にゼロのみが含まれていることがわかっている場合、それらをメモリにまったく保存せず、対応するポインターをnullに設定することができます。 これは、サブ配列が個別に割り当てられる(または割り当てられない)2番目の方法を使用することを意味します。 データが大きい場合、結果として生じるメモリの節約はそれだけの価値があります。

また、3次元以上の配列について説明している場合は、第1、第2、第3の割り当て方法を同時に使用して、異なるレベルの間接化を同時に行うことができます。 最初の方法を使用して2D配列を実装し、2番目の方法を使用してそれらを3D配列に結合することができます。

1


タイトルとURLをコピーしました