web-dev-qa-db-ja.com

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

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

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
Mike

最初のオプション(単一の1D配列)を使用します。これは、何千もの断片化されたメモリブロックではなく、単一のメモリブロックを再生するためです。

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

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

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

最初に、_int ***_を最初の2つの次元サイズとしてxlenおよびylenで解放する関数free_data()を定義します。 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をループして、ith _int **_ポインター_data[i]_を見つけます。次に、指定された_int **_ポインターに対してループし、_int *_でjth _data[i][j]_を見つけて解放します。また、すべての_data[i]_を解放したら、_data[i][j]_を解放する必要があり、最後に、data自体を解放する必要があります。

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

それ以外は、機能は十分にシンプルです。最初にxlen _int **_値にスペースを割り当て、次にこれらのxlenポインターのそれぞれにylen _int *_値にスペースを割り当て、次にこれらの_xlen*ylen_ポインターのそれぞれにスペースを割り当てますzlenint値、_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 <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>

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
Alok Singhal

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
Dims

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

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];

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

ああ、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
AndiDog

Segfaultについては、誰かがこれを指摘していると確信していますが、念のため、最初の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

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

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
Alberto

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

3D配列内の2D配列にメモリを割り当てるときに、割り当てられたメモリを* array [i]ではなくarray [i]に割り当てます。これは、seg障害なしで機能します。

ここにあなたのプログラムがあります

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
Quref

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<column3d;i++)
{
    arr3d[i] = (int**)malloc (column3d*sizeof(int*));
    for (int j = 0;j<height3d;j++)
    {
        arr3d[i][j] = (int*)malloc (height3d*sizeof(int));

        for (int z =0;z<height3d;z++,val3d++)
        {
            arr3d[i][j][z]   = val3d;
        }
    }

}
// De allocation.
for (int i=0;i<row3d;i++)
{
    for(int j=0;j<column3d;j++)
    {
        free(arr3d[i][j]);
    }
}
free(arr3d);
arr3d = 0;
1
ashutosh

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

しかし、なぜ低レベルの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呼び出しを行う代わりに、間接的に各レベルのメモリを事前に「ワンショットで」事前に割り当てることです。後続のサイクルは、事前に割り当てられたメモリをサブ配列に分配するだけです(つまり、ポインタを初期化するだけです)。

ただし、さらに詳しく見ると、実際の配列要素メモリ(実際の値を格納するints)が最初のメソッドとまったく同じ方法で割り当てられていることにも気付くでしょう: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 array:いくつかのサブ配列のパーツが欠けている/使用されていない、またはサイズが小さくなっている、まばらな多次元配列です。たとえば、3D配列の一部の1Dまたは2Dサブ配列にゼロのみが含まれていることがわかっている場合、それらをまったくメモリに格納せず、対応するポインターをnullに設定します。これは、サブ配列が個別に割り当てられる(または割り当てられない)2番目の方法を使用することを意味します。データが大きい場合、結果として生じるメモリの節約はそれだけの価値があります。

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

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

#define MAXX 3
#define MAXY 4
#define MAXZ 5

main()
{
int ***p,i,j;
p=(int ***) malloc(MAXX * sizeof(int **));

for(i=0;i < MAXX;i++)
{
p[i]=(int **)malloc(MAXY * sizeof(int *));
for(j=0;j < MAXY;j++)
p[i][j]=(int *)malloc(MAXZ * sizeof(int));
}

for(k=0;k < MAXZ;k++)
for(i=0;i < MAXX;i++)
for(j=0;j < MAXY;j++)
p[i][j][k]= < something >;

}
0
Vivek Patel

#include "stdlib.h"を追加し、*を* array [i]から削除すると、Ubuntuのgcc 4.4.1でコンパイルしたときに実行されます

また、印刷ステートメントを追加すると、バグをより早く見つけることができます

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

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

  printf("%s\n","OK");

  for (i = 0; i < 3; i++) {
    printf("i = %i \n",i);
    array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      printf("i,j = %i,%i \n",i,j);
      array[i][j] = malloc(3*sizeof(int));
    }
  }

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

  return 0;
}
0
Paul