C言語における多次元配列のメモリ確保
備忘録代わりに今日得た知見をまとめておきます。
前提
今回確保するのは、サイズがあらかじめ決まっている3次元配列です。
通常であれば次のように書けば事足りるのですが、
int a[X][Y][Z];
XYZの値があまりにも大きい場合(大きいサイズの3次元配列を確保しようとした場合)に静的領域に収まらないというケースが発生します。
(私は256GBのメモリのマシンに対して102410244096のfloatの配列を2つ(32GB分)グローバル領域に宣言しようとしてエラーが発生しました。)
なので、今回の記事は動的に要素数が変わる多次元配列の場合は使えないと思います。
動的にメモリを確保する方法
そこでC言語で3次元配列を動的確保したいな、と思いネットの海を検索していたら、どうやらポインタのポインタのポインタを宣言して、for文でそれぞれの階層を回すらしいということがわかりました。
この方法に関する記事は 検索エンジンで "C言語 3次元配列 動的確保"とか検索するとたくさん出てきました。
ただし今回のケースでは、配列のサイズは決まっているのでもう少し簡単に各方法はないんだろうか?
と思って、大学の教授に聞いてみたところ、次のような方法で確保できるらしい、ということがわかりました。
//3次元配列 a[X][Y][Z]の動的確保の方法
#define X 2
#define Y 2
#define Z 2
//---中略---
int (*a)[Y][Z] = (int(*)[Y][Z])malloc(X*Y*Z*sizeof(int));
int型の二次元配列[Y][Z]に対するポインタaに対して、malloc関数によって(XYZ*sizeof(int))byteのメモリを確保します。
malloc関数によって返される型は(void *)型なので、これを(int(*)[Y][Z])でキャストすることによって確保できるみたいです。(二次元配列のアドレスでキャストするっていう考えには全然至らなかった…)
##データの配置
この確保方法で、メモリのアドレスはちゃんと順番通りになっているんだろうか?と思って次のプログラムを実行してみました。
#include <stdio.h>
#include <stdlib.h>
#define X 2
#define Y 2
#define Z 2
int main(void){
int (*a)[Y][Z] = (int(*)[Y][Z])malloc(X*Y*Z*sizeof(int));
//メモリが確保できているか確認
if(a==NULL){
printf("Error\n");
}else{
printf("OK\n");
}
//XYZの値をそれぞれ入れる
for(int i=0;i<X;i++){
for(int j=0;j<Y;j++){
for(int k=0;k<Z;k++){
a[i][j][k] = i*100 + j*10 + k;
}
}
}
printf(" XYZ : Address \n");
//値とアドレスの出力
for(int i=0;i<X;i++){
for(int j=0;j<Y;j++){
for(int k=0;k<Z;k++){
printf("%4d : %p\n",a[i][j][k],&a[i][j][k]);
}
}
}
return 0;
}
実行結果は次のようになりました。
OK
XYZ : Address
0 : 0x10c8010
1 : 0x10c8014
10 : 0x10c8018
11 : 0x10c801c
100 : 0x10c8020
101 : 0x10c8024
110 : 0x10c8028
111 : 0x10c802c
intは4byteなのできちんと連続にデータを取ることができているようです。
forで回したりするよりめっちゃ楽だし、これを使っていこう。