連続したメモリ領域を持つ多次元配列

通常、C言語/C++で多次元配列を作る時は

// 2x3の配列
int a[2][3];
としますね。

この方法はとても便利ですが、要素数は定数でなければならず、開発環境によっては大きなサイズの配列を作ることができません(https://msdn.microsoft.com/ja-jp/library/s0z0bbfe.aspx)。

これを解決するには、mallocなどで配列を動的に確保します。

#include <stdlib.h>

// 2x3の配列
int **b = (int**)malloc(2 * sizeof(int*));
for (int i = 0; i < 2; i++) b[i] = (int*)malloc(3 * sizeof(int*));

// 解放
for (int i = 0; i < 2; i++) free(b[i]);
free(b);
普通に[]でアクセスするのは問題ありませんが、配列まるごとmemcpyしようとするとエラーが起きます。

先程の配列のアドレスを表示してみましょう。

// 表示
printf("b:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%p\n", &b[i][j]);
}
}
printf("\n");
結果はこうなります。
配列版 malloc版
a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
0141FF24
0141FF28
0141FF2C
0141FF30
0141FF34
0141FF38
b[0][0]
b[0][1]
b[0][2]
b[1][0]
b[1][1]
b[1][2]
001C1A10 ←アドレスは
001C1A14 ←4バイトずつ
001C1A18 ←増える
001C1A28 ←アドレスのずれが発生
001C1A2C
001C1A30

原因は5行目でfor文を使って行インデックス別に列要素をmallocで確保しているためです。

メモリ領域を連続にするには、このように行列の全要素を確保した後でインデックスを与えます。

// メモリ領域が連続な2x3の配列
int **c = (int**)malloc(2 * sizeof(int*));
c[0] = (int*)malloc(2 * 3 * sizeof(int));
for (int i = 1; i < 2; i++) c[i] = c[i-1] + 3;

// 解放
free(c[0]);
free(c);
アドレスはこのように、
malloc改良版
c[0][0]
c[0][1]
c[0][2]
c[1][0]
c[1][1]
c[1][2]
002A1A10
002A1A14
002A1A18
002A1A1C
002A1A20
002A1A24

連続していることが確認できます。

おまけ
3次元配列の場合はこのようにしましょう(「良いもの。悪いもの。」様の記事を参考)。

// メモリ領域が連続な2x3x4の配列
int ***d = (int***)malloc(2 * sizeof(int**));
d[0] = (int**)malloc(2 * 3 * sizeof(int*));
d[0][0] = (int*)malloc(2 * 3 * 4 * sizeof(int));
for (int i = 0; i < 2; i++) {
d[i] = d[0] + i * 3;
for (int j = 0; j < 3; j++) d[i][j] = d[0][0] + i * 3 * 4 + j * 4;
}

// 解放
free(d[0][0]);
free(d[0]);
free(d);
なかなかカオスな事になっていますが、4次元だとこうなります。

// メモリ領域が連続な2x3x4x5の配列
int ****e = (int****)malloc(2 * sizeof(int***));
e[0] = (int***)malloc(2 * 3 * sizeof(int**));
e[0][0] = (int**)malloc(2 * 3 * 4 * sizeof(int*));
e[0][0][0] = (int*)malloc(2 * 3 * 4 * 5 * sizeof(int));
for (int i = 0; i < 2; i++) {
e[i] = e[0] + i * 3;
for (int j = 0; j < 3; j++) {
e[i][j] = e[0][0] + i*3*4 + j*4;
for (int k = 0; k < 4; k++) e[i][j][k] = e[0][0][0] + i*3*4*5 + j*4*5 + k*5;
}
}

// 解放
free(e[0][0][0]);
free(e[0][0]);
free(e[0]);
free(e);
なんのこっちゃ。

※2013/01/10
解放がfree()一つで済む実装が「和田維作のホームページ」様のTIPSに紹介されてあります。

// メモリ領域が連続な2次元配列
void *malloc2d(size_t size, int row, int col)
{
char **a, *b;
int t = size * col;

// インデックスと要素を一気に確保
a = (char**)malloc((sizeof(*a) + t) * row);

if (a) {
// [インデックス, インデックス, ..., 要素, 要素, 要素, ...]
// と整列させるため要素の開始位置をずらす
b = (char*)(a + row);

// 各行の先頭アドレスを与える
for (int i = 0; i < row; i++) {
a[i] = b;
b += t; // 要素のサイズ×列の長さの分だけずらす
}

return a;
}

return NULL;
}
これは便利!

int main(void)
{
// メモリ確保
int **f = (int**)malloc2d(sizeof(int), 2, 3);

// 表示
printf("f:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%p\n", &f[i][j]);
}
}
printf("\n");

// 解放
free(f);

return 0;
}
3次元配列版もあります。

// メモリ領域が連続な3次元配列
void *malloc3d(size_t size, int i, int j, int k)
{
char ***a, **b, *c;
int t = size * k;

// インデックスと要素を一気に確保
a = (char***)malloc((sizeof(*a) + sizeof(**a) * j + t * j) * i);

if (a) {
b = (char**)(a + i);
c = (char *)(b + i * j);

for (int idx1 = 0; idx1 < i; idx1++) {
a[idx1] = b;
for (int idx2 = 0; idx2 < j; idx2++) {
b[idx2] = c;
c += t;
}
b += j;
}

return a;
}

return NULL;
}
なるほど、わからん。

最後に4次元配列版をどうぞ。

// メモリ領域が連続な4次元配列
void *malloc4d(size_t size, int i, int j, int k, int l)
{
char ****a, ***b, **c, *d;
int t = size * l;

// インデックスと要素を一気に確保
a = (char****)malloc((sizeof(*a) + sizeof(**a) * j + sizeof(***a) * j * k + t * j * k) * i);
if (a) {
b = (char***)(a + i);
c = (char **)(b + i * j);
d = (char *)(c + i * j * k);
for (int idx0 = 0; idx0 < i; idx0++) {
a[idx0] = b;
for (int idx1 = 0; idx1 < j; idx1++) {
b[idx1] = c;
for (int idx2 = 0; idx2 < k; idx2++) {
c[idx2] = d;
d += t;
}
c += k;
}
b += j;
}

return a;
}

return NULL;
}
書いてて発狂しそうになりました。パターンが分かれば簡単なのですが...

C#みたいにnew int[2][3]とかできたらいいのになぁ。
関連記事

コメントの投稿

非公開コメント

No title

スタック領域の制限が0x7fffffffという規定はISO/IEC9899にもISO/IEC14882にもありませんし、特にフリースタンディング環境ではスタックサイズを自分で決めねばリンクすらできません。つまりスタックサイズの制限を理由に、本来希望する記憶クラスを諦めてmallocやstaticを使えという主張は誤りです。

Re: No title

コメントありがとうございます。

当時、実行環境がVisual Studioで下記のコンパイルエラーに直面したのが主張の理由だったと思うのですが、よく考えたらスタックサイズは関係ありませんでしたね。記事を修正しますのでしばらくお待ちください。
https://msdn.microsoft.com/ja-jp/library/s0z0bbfe.aspx

大変助かりました

ESP32マイコンArduinoで静的確保ではメモリ不足になり、どうしても動的確保して4次元配列で使用する必要があり、困っていました。
4次元配列版頂きました。大変助かります!ありがとうございました!!
プロフィール

puku

Author:puku
暇な時はゲームかプログラミングしてる人だよ。
だいたい月1更新。
CV Drone はこちら(GitHub)

最近はQiitaでOnsenUI2で遊んでいる。

最新記事
最新コメント
最新トラックバック
検索フォーム
カレンダー
02 | 2023/03 | 04
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
月別アーカイブ
カテゴリ
スポンサードリンク
RSSリンクの表示
FC2カウンター
リンク
ブロとも申請フォーム

この人とブロともになる

アクセスランキング
[ジャンルランキング]
コンピュータ
754位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
158位
アクセスランキングを見る>>
FC2ブログランキング

FC2Blog Ranking

QRコード
QR