C言語の動的配列のリサイズ方法
目次
C言語の動的配列のリサイズ方法
C言語で動的なメモリの確保で配列のメモリを確保すると、実行時に可変長な長さの配列を作ることが出来ます。
この配列を「動的配列」とか「可変長配列」などといいます。
高度なC言語のプログラミングでは、この動的配列を使う機会が非常に多いです。
モジュールを動的配列で実装することで、そのモジュールの設計における柔軟性が向上し、便利になります。
この記事ではC言語による動的配列の作り方と、その動的配列をリサイズする方法を解説します。
具体的には↓を見ていきます。
動的配列の作り方
動的配列のリサイズ方法
動的配列の作り方
動的な配列とは、「配列の長さ」が「動的に決まる」という意味の配列です。
普通はC言語の配列はサイズが固定です。要素数を指定したり、有限のデータを使って初期化したりします。
C言語で動的な配列を作るというのは「メモリを動的に確保する」ということになります。
C言語の標準ライブラリstdlib.h
には動的なメモリを確保するための関数があります。
それはmalloc()
やcalloc()
などです。
この記事ではcalloc()
を使った動的メモリの確保を解説します。
まず最初にcalloc()
の仕様を見てみましょう。
calloc()の仕様
calloc()
は↓のような作りになっています。
void * calloc(size_t nmemb, size_t size);
calloc()
は第1引数にメンバ数(要素数)をとります。
そして第2引数にメンバ1つ分のサイズを取ります。サイズはバイト数です。
たとえば第1引数に1
を指定して、第2引数に100
を指定した場合、確保されるメモリのバイト数は1 * 100
で100
バイトになります。
第1引数が4
で第2引数が100
なら4 * 100
で400
バイト分のメモリが確保されます。
calloc()
はメモリの確保に成功すると確保したメモリへのポインタを返します。これはvoid *
型です。
メモリの確保に失敗した場合はNULL
を返します。また、errno
にENOMEM
をセットします。
確保したメモリはfree()
関数で解放する必要があります。解放しない場合はメモリリークになります。
calloc()
はmalloc()
と違い、確保したメモリを0
クリアします。
calloc()で動的配列を作る
ではcalloc()
を使って動的配列を作ってみます。
↓のようにコードを書きます。
#include <stdio.h> #include <stdlib.h> int main(void) { int nmemb = 4; // 配列の要素数 int size = sizeof(int); // 要素1つのバイト数 int *arr = calloc(nmemb, size); // 動的にメモリを確保 if (!arr) { perror("calloc"); return 1; } // 確保した配列の中身を見る for (int i = 0; i < nmemb; i++) { printf("[%d] = %d\n", i, arr[i]); } free(arr); // メモリを解放 return 0; }
この↑のコードの実行結果は↓のようになります。
[0] = 0 [1] = 0 [2] = 0 [3] = 0
↓の部分を見てみます。
int nmemb = 4; // 配列の要素数 int size = sizeof(int); // 要素1つのバイト数 int *arr = calloc(nmemb, size); // 動的にメモリを確保 if (!arr) { perror("calloc"); return 1; }
変数nmemb
は確保する動的配列の要素数、変数size
は要素1つ分のバイト数です。
それらの変数をcalloc()
に渡しています。
calloc()
の返り値をint *arr
に入れていますが、void *
型の返り値はこのように異なる型へ自由にキャストすることができます(void *
じゃなくてもキャストはできます)。
arr
がNULL
だった場合はメモリの確保に失敗しているので、↑のようにperror()
でerrno
を参照し、return 1;
します。
次に↓の部分を見てみます。
// 確保した配列の中身を見る for (int i = 0; i < nmemb; i++) { printf("[%d] = %d\n", i, arr[i]); }
↑の部分では確保した配列arr
の中身を出力しています。
nmemb
が要素数なのでその要素数分ループを回して、添え字でアクセスします。
最後に↓の部分です。
free(arr); // メモリを解放 return 0;
↑のようにcalloc()
で確保したメモリは解放するようにします。
動的配列のリサイズ方法
では本題の動的配列のリサイズ方法です。
C言語では動的配列のリサイズにはrealloc()
関数を使います。
まず最初にrealloc()
の仕様を見てみましょう。
realloc()の仕様
realloc()
は↓のような作りになってます。
void * realloc(void *ptr, size_t size);
第1引数のptr
にはすでにある動的配列のポインタを渡します。
第2引数のsize
には確保するメモリのバイト数を渡します。このバイト数は「再確保する分のバイト数」ではなく、「確保するメモリ全体のバイト数」であることに注意してください。
realloc()
は第1引数に渡されたポインタのメモリをsize
のバイト数で再確保し、その結果をvoid *
で返します。
返り値のポインタに保存されているメモリのアドレスと、引数ptr
に保存されているメモリのアドレスは異なっています。
再確保した場合、引数ptr
のポインタをfree()
する必要はなく、かわりに返り値のポインタをfree()
する必要が出てきます。
realloc()
はメモリの確保に成功した場合、先述のように確保したメモリのポインタを返します。
メモリの確保に失敗した場合はNULL
を返します。また、errno
にENOMEM
をセットします。
realloc()で動的配列をリサイズする
ではrealloc()
で動的配列をリサイズしてみます。
↓のようにコードを書きます。
#include <stdio.h> #include <stdlib.h> int main(void) { int nmemb = 4; // 配列の要素数 int size = sizeof(int); // 要素1つのバイト数 int *arr = calloc(nmemb, size); // 動的にメモリを確保 if (!arr) { perror("calloc"); return 1; } // 確保した配列の値を初期化 for (int i = 0; i < nmemb; i++) { arr[i] = i * 10; printf("[%d] = %d\n", i, arr[i]); } puts("----"); // 動的配列をリサイズする nmemb = 8; // 要素数を倍にする size = nmemb * sizeof(int); // 確保するバイト数 int *tmp = realloc(arr, size); // 配列をリサイズ if (!tmp) { free(arr); // 元の配列を解放する perror("realloc"); return 1; } arr = tmp; // リサイズしたポインタをarrに保存 // リサイズした配列の中身を見る for (int i = 0; i < nmemb; i++) { printf("[%d] = %d\n", i, arr[i]); } free(arr); // メモリを解放 return 0; }
↑のコードを実行すると↓のような結果になります。
[0] = 0 [1] = 10 [2] = 20 [3] = 30 ---- [0] = 0 [1] = 10 [2] = 20 [3] = 30 [4] = 0 [5] = 0 [6] = 0 [7] = 0
valgrind
などのメモリチェックツールによっては「Conditional jump or move depends on uninitialised value」などのエラーが出ますが、これは初期化されていない配列の要素をprintf()
で参照しているからです。
今回は簡便さのためにリサイズした要素は未初期化にしてあります。
(^ _ ^) | ↑の出力を見る限りは初期化されているように見えるけどな |
(・ v ・) | パソコンの7不思議さ |
まず↓の部分を見てみます。
int nmemb = 4; // 配列の要素数 int size = sizeof(int); // 要素1つ分のバイト数 int *arr = calloc(nmemb, size); // 動的にメモリを確保 if (!arr) { perror("calloc"); return 1; }
↑の部分はcalloc()
の解説の通りです。
次に↓の部分です。
// 確保した配列の値を初期化 for (int i = 0; i < nmemb; i++) { arr[i] = i * 10; printf("[%d] = %d\n", i, arr[i]); } puts("----");
今回は結果の配列を見やすくするため、↑のようにarr[i] = i * 10;
とやって配列の要素を初期化しています。
あとはcalloc()
の解説と同じです。
次に↓の部分です。
// 動的配列をリサイズする nmemb = 8; // 要素数を倍にする size = nmemb * sizeof(int); // 確保するバイト数 int *tmp = realloc(arr, size); // 配列をリサイズ if (!tmp) { free(arr); // 元の配列を解放する perror("realloc"); return 1; } arr = tmp; // リサイズしたポインタをarrに保存
realloc()
で配列をリサイズしています。
まず変数nmemb
に確保したい配列の要素数を保存しています。今回は8
です。
それから変数size
に配列全体のサイズを指定します。これは要素数と要素1つのサイズから計算します。
今回作っているのはint
型の配列なので、要素1つのサイズはsizeof(int)
で得ることが出来ます。
そのサイズをnmemb
, つまり要素数にかければ配列全体のサイズが求まります。
int *tmp = realloc(arr, size); if (!tmp) { free(arr); // 元の配列を解放する perror("realloc"); return 1; }
↑の部分では、realloc()
の第1引数にすでにあるarr
を渡し、第2引数に計算したメモリのサイズを渡します。
そしてその返り値をint *tmp
に入れています。
わざわざint *tmp
に保存している理由ですが、仮にarr
で返り値を受けてしまうと、返り値がNULL
だった場合にarr
のメモリを解放できなくなります。そのため↑のように一時的なtmp
という変数を作って、そこに保存しています。
tmp
がNULL
だった場合はrealloc()
に失敗しているので、元の配列arr
を解放し、errno
をperror()
で参照したあとにreturn 1;
としています。
arr = tmp; // リサイズしたポインタをarrに保存
メモリの確保に成功した場合は、↑のようにtmp
を忘れずにarr
に代入しておきます。
こうすることでarr
はリサイズされた配列として機能します。
次に↓の部分です。
// リサイズした配列の中身を見る for (int i = 0; i < nmemb; i++) { printf("[%d] = %d\n", i, arr[i]); }
リサイズした配列の中身を出力しています。
最後に↓の部分です。
free(arr); // メモリを解放 return 0;
arr
をfree()
しています。
途中のtmp
は解放する必要はありません。解放した場合はダブルフリーになります。
出力結果をみると、配列がリサイズされ拡張されているのがわかると思います。
このようにrealloc()
を使うと動的配列をリサイズすることができます。
おわりに
realloc()
の使い方はちょっと注意が必要ですが、慣れればストレスなく使えるようになります。
動的配列を使えるようになると便利なモジュールを作れるようになります。
(^ _ ^) | 配列が伸びるよどこまでも |
(・ v ・) | キノピオの鼻みたいだぁ |