動的にメモリを確保する

C言語で動的にメモリを確保する方法の紹介です。C言語では、実行時にしかサイズがわからないデータ構造を表現するのに、動的なメモリ確保を行う必要があります。

C言語の配列は静的な配列で、コンパイル時にサイズが決定されます。文字列リテラルもコンパイル時にサイズが決定されます。

実務のプログラミングでは、実行時にしかサイズがわからない動的なデータを求められることがほとんどです。要素数を実行時に決定したい場合は動的な配列を作成する必要がありますし、文字数を実行時に決定したい動的文字列を作成するには、基本として動的なメモリ確保を覚える必要があります。

動的にメモリを確保する

動的にメモリを確保するには、stdlib.hヘッダで定義されているmalloc関数またはcalloc関数を使用します。

C99によるC言語入門では、メモリ領域の0初期化を行ってくれるcalloc関数をお勧めしています。calloc関数を使って、動的にメモリを確保する方法を解説します。

calloc関数で動的にメモリを確保してみましょう。char型(1バイト)で、4の長さのメモリ領域を確保します。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main(void) {
  // 動的なメモリの確保
  int32_t capacity = 4;
  char* bytes = calloc(sizeof(char), capacity);
  
  // 確保されたメモリ領域を使用。確保した領域を超えないように注意。最初はゼロ初期化されている。
  bytes[2] = 5;
  printf("%d %d %d\n", bytes[0], bytes[1], bytes[2]);
}

出力結果です。

0 0 5

次は、int32_t型(4バイト)で、3の長さのメモリ領域を確保してみます。4×3で、12バイトのメモリが確保されます。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main(void) {
  // 動的なメモリの確保
  int32_t capacity = 3;
  int32_t* nums = calloc(sizeof(int32_t), capacity);
  
  // 確保されたメモリ領域を使用。確保した領域を超えないように注意。最初はゼロ初期化されている。
  nums[2] = 7;
  printf("%d %d %d\n", nums[0], nums[1], nums[2]);
}

出力結果です。

0 0 7

確保したメモリを解放する

確保したメモリ領域は、不要になれば、free関数を使って解放します。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

int main(void) {
  // 動的なメモリの確保
  int32_t capacity = 4;
  char* bytes = calloc(sizeof(char), capacity);
  
  // 処理
  
  // 不要になったらfreeで解放
  free(bytes);
}

確保したメモリ領域が不要になるタイミングは一概にはいえません。プログラムの最初から最後まで必要なデータの場合は、プログラムを終了する直前に解放します。

また、プログラム中で、繰り返しメモリ領域を確保して、不要になる場合は、不要になったタイミングで、解放します。

たとえば、HTTPリクエストが送られてきて、HTTPリクエストのデータを構造体で表現して、処理が終わったらHTTPリクエストのデータは不要になるような場合を考えてみてください。このような場合は、HTTPリクエストが送られてきたタイミングで、メモリを確保し、不要になったタイミングでメモリを解放します。

動的な配列の作成

動的なメモリ確保の一つのサンプルとして、動的にメモリを確保して、要素を追加できる動的な配列を作成してみましょう。要素のデータ型はint32_t型にします。配列の最初のメモリ領域の長さは4にします。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
  // 配列のメモリ領域の長さ
  int32_t capacity = 4;
  
  // 配列の最後のインデックス
  int32_t last_index = 0;
  
  // 動的にメモリ確保
  int32_t* nums = calloc(capacity, sizeof(int32_t));
  
  // 配列に要素を追加
  nums[last_index] = 3;
  last_index++;
  
  // 配列に要素を追加
  nums[last_index] = 8;
  last_index++;
  
  // 配列に要素を追加
  nums[last_index] = 7;
  last_index++;
  
  // 配列に要素を追加
  nums[last_index] = 15;
  last_index++;
  
  // last_indexは4になっており、配列のcapacityを超えています。
  
  // メモリ領域を拡張します
  // 2倍のサイズのメモリ領域を新しく確保
  int32_t new_capacity = capacity * 2;
  int32_t* new_nums = calloc(new_capacity, sizeof(int32_t));
  
  // メモリ領域を現在のものから、新しいものへコピー
  memcpy(new_nums, nums, capacity * sizeof(int32_t));
  
  // 現在のメモリ領域を解放
  free(nums);
  
  // 新しいメモリ領域を現在のメモリ領域に設定
  nums = new_nums;
  
  // 間違って使わないようにNULLへ
  new_nums = NULL;
  
  // これで新しく要素を追加できる
  
  // 配列に要素を追加
  nums[last_index] = 20;
  last_index++;
  
  for (int32_t i = 0; i < last_index; i++) {
    printf("%d\n", nums[i]);
  }
}

出力結果です。

3
8
7
15
20

これをfor文関数で表現できるようになれば、動的な配列のライブラリを自作できるようになりますね。

動的配列と書きましたが、後から、実装をよく眺めてみると、まるでスタックを表現しているかのようですね(あら)。

関連情報