C で動的多次元配列を操作するにはどうすればよいですか?

C99 以降、C には動的境界を持つ 2D 配列があります。そのようなビーストがスタックに割り当てられるのを避けたい場合 (そうするべきです)、次のように一度に簡単に割り当てることができます

double (*A)[n] = malloc(sizeof(double[n][n]));

以上です。 A[i][j] のようなもので 2D 配列に使用されているように、簡単に使用できます。 .最後に忘れないでください

free(A);

Randy Meyers が 可変長配列 を説明する一連の記事を書きました (VLA)。


malloc を使用した動的割り当て:

int** x;

x = malloc(dimension1_max * sizeof(*x));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = malloc(dimension2_max * sizeof(x[0]));
}

//Writing values
x[0..(dimension1_max-1)][0..(dimension2_max-1)] = Value; 
[...]

for (int i = 0; i < dimension1_max; i++) {
  free(x[i]);
}
free(x);

これは、サイズ dimension1_max の 2D 配列を割り当てます * dimension2_max .したがって、たとえば、640*480 の配列 (イメージのピクセルなど) が必要な場合は、dimension1_max を使用します。 =640、dimension2_max =480. x[d1][d2] を使用して配列にアクセスできます。 どこで d1 =0..639、d2 =0..479.

ただし、SO または Google で検索すると、他の可能性も明らかになります。たとえば、この SO の質問

その場合、配列はメモリの連続した領域 (640*480 バイト) を割り当てないことに注意してください。これを前提とする関数で問題が発生する可能性があります。したがって、条件を満たす配列を取得するには、上記の malloc ブロックを次のように置き換えます。

int** x;
int* temp;

x = malloc(dimension1_max * sizeof(*x));
temp = malloc(dimension1_max * dimension2_max * sizeof(x[0]));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = temp + (i * dimension2_max);
}

[...]

free(temp);
free(x);

基本

c の配列は、[] を使用して宣言およびアクセスされます。 オペレーター。だから

int ary1[5];

5 つの整数の配列を宣言します。要素にはゼロから番号が付けられるため、ary1[0] が最初の要素で、ary1[4] 最後の要素です。注 1:デフォルトの初期化がないため、配列が占有するメモリには最初に 何か が含まれている可能性があります .注2:ary1[5] 未定義の状態でメモリにアクセスします (アクセスできない場合もあります)。そうしないでください!

多次元配列は、配列の配列 (of arrays (of ... ) ) として実装されます。だから

float ary2[3][5];

それぞれ 5 つの浮動小数点数の 3 つの 1 次元配列の配列を宣言します。 ary2[0][0]になりました 最初の配列の最初の要素、ary2[0][4] は最初の配列の最後の要素で、ary2[2][4] 最後の配列の最後の要素です。 '89 標準では、このデータが連続している必要があります (私の K&R 第 2 版の 216 ページの A8.6.2 節) が、パディングにとらわれないようです。

複数の次元でダイナミックに行こうとする

コンパイル時に配列のサイズがわからない場合は、配列を動的に割り当てる必要があります。やってみたくなる

double *buf3;
buf3 = malloc(3*5*sizeof(double));
/* error checking goes here */

これは、コンパイラが割り当てをパディングしない (1 次元配列間に余分なスペースを入れる) 場合に機能するはずです。以下を使用する方が安全かもしれません:

double *buf4;
buf4 = malloc(sizeof(double[3][5]));
/* error checking */

しかし、いずれにせよ、トリックは逆参照時に発生します。 buf[i][j] と書くことはできません なぜなら buf の型が間違っています。使用することもできません

double **hdl4 = (double**)buf;
hdl4[2][3] = 0; /* Wrong! */

コンパイラは hdl4 を想定しているため double のアドレスのアドレスになります。 double incomplete_ary4[][]; も使用できません これはエラーです;

では、何ができるでしょうか?

  • 行と列の計算を自分で行う
  • 関数内で作業を割り当てて実行する
  • ポインターの配列を使用する (qrdl が話しているメカニズム)

自分で計算してください

次のように、各要素へのメモリ オフセットを計算するだけです:

  for (i=0; i<3; ++i){
     for(j=0; j<3; ++j){
        buf3[i * 5 + j] = someValue(i,j); /* Don't need to worry about 
                                             padding in this case */
     }
  }

関数で作業を割り当てて実行する

必要なサイズを引数として受け取る関数を定義し、通常どおり続行します

void dary(int x, int y){
  double ary4[x][y];
  ary4[2][3] = 5;
}

もちろん、この場合は ary4 はローカル変数であり、それを返すことはできません:配列に関するすべての作業は、それ を呼び出す関数内で行う必要があります

ポインタの配列

これを考慮してください:

double **hdl5 = malloc(3*sizeof(double*));
/* Error checking */
for (i=0; i<3; ++i){
   hdl5[i] = malloc(5*sizeof(double))
   /* Error checking */
}

hdl5 それぞれが double の配列を指すポインターの配列を指します。クールな点は、2 次元配列表記を使用してこの構造にアクセスできることです ---hdl5[0][2] 最初の行の中央の要素を取得します --- ただし、これは double ary[3][5]; で宣言された 2 次元配列とは異なる種類のオブジェクトです .

この構造は 2 次元配列よりも柔軟ですが (行が同じ長さである必要がないため)、アクセスは一般的に遅くなり、より多くのメモリが必要になります (中間ポインターを保持する場所が必要です)。

ガードを設定していないため、すべての配列のサイズを自分で追跡する必要があることに注意してください。

算術

c は、ベクトル、行列、またはテンソル演算をサポートしていません。自分で実装するか、ライブラリを導入する必要があります。

スケーラーによる乗算と、同じランクの配列の加算と減算は簡単です。要素をループして、必要に応じて操作を実行するだけです。内積も同様に簡単です。

外積はより多くのループを意味します。