記憶は単なる記憶であることを忘れないでください。陳腐に聞こえるかもしれませんが、非常に多くの人が C でのメモリ割り当てとメモリ管理を魔法のブードゥー教のように考えているようです。そうではありません。一日の終わりに、必要なメモリを割り当て、作業が終わったら解放します。
それでは、最も基本的な質問から始めましょう。「n」double
値が必要な場合、それらをどのように割り当てますか?
double *d1d = calloc(n, sizeof(double));
// ... use d1d like an array (d1d[0] = 100.00, etc. ...
free(d1d);
十分に単純です。次の質問は、2 つの部分で構成されます。最初の部分は(まだ)メモリ割り当てとは関係ありません。
- サイズ
double
の 2D 配列にはいくつの値がありますか?m*n
- それらすべてを保持するのに十分なメモリをどのように割り当てることができますか。
答え:
m*n
double の am*n 2D 行列に doubleがあります
- (m*n) double を保持するのに十分なメモリを割り当てます。
十分に単純に思えます:
size_t m=10;
size_t n=20;
double *d2d = calloc(m*n, sizeof(double));
しかし、実際の要素にアクセスするにはどうすればよいでしょうか? 少し数学が整っています。m
とを知ってn
いれば、これを簡単に行うことができます
size_t i = 3; // value you want in the major index (0..(m-1)).
size_t j = 4; // value you want in the minor index (0..(n-1)).
d2d[i*n+j] = 100.0;
これを行う簡単な方法はありますか?標準 C では、はい。C++ ではいいえ。標準 C は、動的サイズのインデックス可能な配列を宣言するための適切なコードを生成する非常に便利な機能をサポートしています。
size_t m=10;
size_t n=20;
double (*d2d)[n] = calloc(m, sizeof(*d2d));
これを十分に強調することはできません: 標準 C はこれをサポートしていますが、C++ はサポートしていません。C++ を使用している場合は、オブジェクト クラスを作成してこれらすべてを実行することをお勧めします。そのため、それ以上は説明しません。
では、上記の実際の機能は何をするのでしょうか? まず、以前に割り当てていたのと同じ量のメモリをまだ割り当てていることは明らかです。つまり、m*n
要素、それぞれがsizeof(double)
大きいです。しかし、おそらく「その変数宣言は何だろう?」と自問しているでしょう。それには少し説明が必要です。
これには明確で現在の違いがあります。
double *ptrs[n]; // declares an array of `n` pointers to doubles.
この:
double (*ptr)[n]; // declares a pointer to an array of `n` doubles.
コンパイラは各行の幅(各行の double)を認識するようになったため、 2 つのインデックスn
を使用して配列内の要素を参照できるようになりました。
size_t m=10;
size_t n=20;
double (*d2d)[n] = calloc(m, sizeof(*d2d));
d2d[2][5] = 100.0; // does the 2*n+5 math for you.
free(d2d);
これを 3D に拡張できますか? もちろん、数学は少し奇妙に見え始めますが、それでも計算を大きなブロックオラムにオフセットするだけです。最初に、[i、j、k] でインデックスを作成する「自分で計算する」方法を使用します。
size_t l=10;
size_t m=20;
size_t n=30;
double *d3d = calloc(l*m*n, sizeof(double));
size_t i=3;
size_t j=4;
size_t k=5;
d3d[i*m*n + j*m + k] = 100.0;
free(d3d);
double
RAMの大きなブロックの値が実際にどこにあるのかを計算する方法を実際にゲル化するには、その数学を1分間見つめる必要があります. 上記のディメンションと必要なインデックスを使用すると、「生の」インデックスは次のようになります。
i*m*n = 3*20*30 = 1800
j*m = 4*20 = 80
k = 5 = 5
======================
i*m*n+j*m+k = 1885
つまり、その大きな線形ブロックの 1885 番目の要素に到達しています。別のことをしましょう。[0,1,2] はどうですか?
i*m*n = 0*20*30 = 0
j*m = 1*20 = 20
k = 2 = 2
======================
i*m*n+j*m+k = 22
つまり、線形配列の 22 番目の要素です。
配列の自己規定範囲内にとどまっている限り、i:[0..(l-1)], j:[0..(m-1)], and k:[0..(n-1)]
有効なインデックストリオは線形配列内で一意の値を見つけ、他の有効なトリオも見つけられないことは明らかです。
最後に、以前に 2D 配列で行ったのと同じ配列ポインター宣言を使用しますが、それを 3D に拡張します。
size_t l=10;
size_t m=20;
size_t n=30;
double (*d3d)[m][n] = calloc(l, sizeof(*d3d));
d3d[3][4][5] = 100.0;
free(d3d);
繰り返しますが、これが実際に行うことは、以前に手動で行っていたのと同じ計算ですが、コンパイラーに任せています。
頭を包み込むのは少し難しいかもしれませんが、重要です。連続したメモリ マトリックスを使用することが最も重要な場合 (OpenGL などのグラフィックス レンダリング ライブラリにマトリックスを供給するなど)、上記の手法を使用して比較的簡単に実行できます。
最後に、このようにできるのに、ポインター配列全体をポインター配列からポインター配列から値に変換するのはなぜだろうかと疑問に思うかもしれません。多くの理由。行を置き換えるとします。ポインターの交換は簡単です。行全体をコピーしますか? 高い。(m*n)
3D 配列内のテーブル ディメンション全体を置き換えていると仮定すると(l*n*m)
、ポインタを交換するのはさらに簡単です。m*n
テーブル全体をコピーしますか? 高価です。そして、それほど明白ではない答え。行幅を行ごとに独立させる必要がある場合 (つまり、row0 は 5 つの要素、row1 は 6 つの要素にすることができます)。その場合、固定l*m*n
割り当ては単に機能しません。
幸運を祈ります。