実際のプログラミングでは、必要なメモリ容量が実際に入力されたデータに依存し、事前に決定できないという状況がよく発生します。このような問題は、静的配列の方法では解決が困難です。上記の問題を解決するために、C言語はいくつかのメモリ管理関数を提供しています。これらのメモリ管理関数はポインタと組み合わせることで、必要に応じてメモリ空間を動的に割り当て、動的配列を構築したり、不要になった空間を回収して再利用したりすることができ、メモリ資源を効果的に利用する手段を提供します。
動的配列は、静的配列に対する概念です。静的配列の長さは事前に定義されており、プログラム全体を通じて、サイズを与えると変更できません。一方、動的配列はそうではなく、プログラムの必要性に応じてサイズを再指定できます。動的配列のメモリ空間はヒープ(heap)から割り当てられます(つまり動的割り当てです)。コードを実行することによってストレージ空間が割り当てられます。プログラムがこれらの文を実行したときに初めて割り当てられます。メモリの解放はプログラマ自身が責任を持って行います。
なぜ動的配列を使うのか?
実際のプログラミングでは、必要なメモリ容量が実際に入力されたデータに依存し、事前に決定できないという状況がよく発生します。このような問題は、静的配列の方法では解決が困難です。上記の問題を解決するために、C言語はいくつかのメモリ管理関数を提供しています。これらのメモリ管理関数はポインタと組み合わせることで、必要に応じてメモリ空間を動的に割り当て、動的配列を構築したり、不要になった空間を回収して再利用したりすることができ、メモリ資源を効果的に利用する手段を提供します。
動的配列と静的配列の比較
静的配列は、作成が非常に簡単で、使用後も解放する必要がなく、参照も簡単ですが、作成後にサイズを変更できないのが致命的な弱点です!
動的配列は、作成が面倒で、使用後はプログラマ自身が解放しなければならず、そうしないと深刻な場合メモリリークを引き起こします。しかし、その使用は非常に柔軟で、プログラムの必要に応じてサイズを動的に割り当てることができます。
動的配列の構築方法
従うべき原則
確保するときは外側から内側へ、層ごとに確保します;
解放するときは内側から外側へ、層ごとに解放します。
構築に必要なポインタ
1次元の動的配列を構築するには、1次元ポインタが必要です;
2次元の場合は、1次元および2次元ポインタが必要です;
3次元の場合は、1次元、2次元、3次元ポインタが必要です;
以下同様に続きます。
構築に必要な関数
| 関数プロトタイプ | 戻り値 | 機能説明 |
| void *malloc(unsigned int size); | 成功:確保した領域の先頭アドレスを返す 失敗:ヌルポインタを返す | システムにsizeバイトのヒープ領域を要求する |
| void *calloc(unsigned int num, unsigned int size); | 成功:確保した領域の先頭アドレスを返す 失敗:ヌルポインタを返す | 型に応じてnum個のsizeバイトのヒープ領域を要求する |
| void free(void *p); | 戻り値なし | pが指すヒープ領域を解放する |
| void *realloc(void *p,unsigned int size); | 成功:新しく確保した領域の先頭アドレスを返す 失敗:ヌルポインタを返す | pが指すヒープ領域をsizeに変更する |
説明:
-
(1)void * 型と規定されていますが、これは関数呼び出し後に戻り値がないという意味ではなく、ノードのアドレスを返すことを意味します。そのアドレスの型は void(型なし、または型が不定)です。つまり、ある記憶領域の先頭アドレスであり、その具体的な型は決定できず、使用時に各フィールドの値のデータに応じて決定されます。キャストを使用して他の型に変換できます。例:double *pd=NULL; pd=(double *)calloc(10,sizeof(double)); これは、システムに10個の連続したdouble型の記憶領域を要求し、ポインタpdを使ってこの連続領域の先頭アドレスを指すことを示します。また、(double) で calloc() の戻り値の型を変換して、double型データのアドレスをポインタpdに代入できるようにしています。
-
(2)sizeofを使用する目的は、ある型が占有するバイト数を計算し、異なるコンパイラに対応できるようにすることです。
(3)動的割り当ては必ずしも成功するとは限らないため、プログラムの実行が停止してユーザーが戸惑うことのないように、例外処理プログラムを追加する必要があります。通常、次のような例外処理プログラムセグメントを使用します。 if(p==NULL) /* または if(!p) */ { printf("動的メモリの割り当てに失敗しました!\n"); exit(1); //異常終了 }
-
(4)これら4つの関数のヘッダファイルはすべて
に含まれています。 -
(5)割り当てられたヒープ領域には名前がなく、返されたポインタを介してのみそれを見つけることができます。
-
(6)動的割り当てではない記憶ブロックに対して free を使用してはなりません。また、同じメモリ領域に対して free を2回同時に使用して解放することもできません。例:free(p);free(p);
(7)free() を呼び出すとき、渡されたポインタが指すメモリは解放されますが、呼び出し元のポインタの値は変わらない可能性があります。p は仮引数として関数に渡されるためです。厳密に言うと、解放されたポインタの値は無効です。なぜなら、それはもはや要求したメモリ領域を指していないからです。このとき、それを使用すると問題が発生する可能性があります。
mallocとcallocの違い
malloc で割り当てられたメモリ領域については、以前に使用されたことがなければ、その中の各ビットはすべて0である可能性があります。逆に、このメモリ領域が以前に割り当て、解放、再割り当てされたことがある場合は、さまざまなデータが残っている可能性があります。つまり、malloc() 関数を使用するプログラムは、開始時(メモリ領域がまだ再割り当てされていない)には正常に動作しますが、しばらくすると(メモリ領域が再割り当てされた後)問題が発生する可能性があります。そのため、使用する前に必ず初期化する必要があります(memset 関数を使用して0に初期化できます)。しかし、calloc() 関数を呼び出して割り当てられた領域は、割り当て時にすでに0に初期化されています。 calloc() 関数と malloc() 関数のどちらを選ぶかは、割り当てられたメモリ領域を初期化する必要があるかどうかを考慮して、対応する関数を選択する必要があります。
具体的な構築方法
3次元の整数配列 array[n1][n2][n3] を例にします。
まず、外側から内側へ、層ごとに確保するという原則に従います:
最も外側のポインタは array で、これは3次元ポインタです。指しているのは array[] で、これは2次元ポインタです。したがって、array に対して
メモリを確保するには次のようにします:
array=(int***)calloc(n1,sizeof(int**));
次層のポインタは array[] で、これは2次元ポインタです。指しているのは array[][] で、これは1次元ポインタです。したがって、array[] に対して
メモリを確保するには次のようにします:
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
}
最も内側のポインタは array[][] で、これは1次元ポインタです。指しているのは array[][][] で、これは整数定数です。したがって、array[][] に対してメモリを確保するには次のようにします:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
}
}
もちろん、これらをまとめて次のように書くこともできます:
int i,j,k;
int n1,n2,n3;
int ***array;
scanf("%d%d%d",&n1,&n2,&n3);
array=(int***)calloc(n1,sizeof(int**));
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
for(k=0;k<n3;k++)
{
array[i][j][k]=i+j+k+1;
}
}
}
最後に、これらのメモリを解放することを忘れないでください。これは、解放するときは内側から外側へ、層ごとに解放するという原則に従う必要があります。
解析のプロセスは上記の解答を参照してください。ここでは詳しく説明しません。コードだけを示します:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
free(array[i][j]);//释放第三维指针
}
}
for(i=0;i<n1;i++)
{
free(array[i]);//释放第二维指针
}
free(array);//释放第一维指针
他の次元、例えば4次元の作成プロセスはほぼ同じなので、ここでは詳しく説明しません。