見出し画像

C言語教室 第7回 - 動的なメモリ割り当て

今までのようにC言語自身が持っている機能を使ってプログラムを書くと、必要なメモリの大きさは必ず事前にわかっており、もしわからない場合には最大の大きさを割り当てておいて、その範囲内で使うことになります。

このようなメモリの使い方を一般的に「静的」と呼びます。このやり方の良いところは、事前に使用するメモリの大きさがわかっているので、プログラムを実行するのに必要なメモリの大きさが決められることです。これは特に小規模なシステムにおいては大事なことで、組み込みなどに使われるプログラムにおいては、利用するすべてのメモリを静的に割り当てることが求められる場合もあります。

しかしながら、特にキーボードやファイルなど外部からデータを取り込むときなどは、事前に大きさがわからないことが普通なので、静的なメモリの割り当てはとても使いにくいものです。

そこでライブラリ関数を使うことで、システムから必要な時に必要な大きさのメモリを割り当ててもらい、それを使う方法があります。これは使わなくなったメモリをシステムに戻す関数とセットで使います。ここで割り当てられたメモリはシステムというグローバルなところから割り当てるので、C言語でいうところのグローバル変数のように働きます。関数を終了してもプログラムが継続する限り、割り当てられたメモリは残ります(プログラムが終了した時点で、そのプログラムに割り当てられていたメモリは返却されます)。

このようにして割り当てることを「動的」と呼びます。動的なメモリは事前の大きさを決めておく必要がないので、便利に使えるのですが、必ずしも割り当てられるメモリが残っているとは限らず、場合によって割り当てができないこともあります。また不要になったメモリを「正しく」返さないといけません。ここに誤りがあると、プログラムを使い続けると次第に割り当てたメモリが増え続け、いつかはシステムからメモリを割り当ててもらえなくなります。

さて、いろいろと小難しいことを書きましたが、まずは使ってみましょう。メモリを割り当てる関数は malloc() 、返す関数は free() です。これらの関数を使う場合には、”stdlib.h” というヘッダファイルをインクルードする必要があります。

整数の配列を動的に割り当てて使ってみましょう。まずは静的なコードを書いてみてから、直してみましょう。

#include <stdio.h>

void main() {
  int a[3];
  int i;

  a[0] = 1;
  a[1] = 2;
  a[2] = 4;

  for ( i = 0; i < 3; i++)
    printf(“a[%d]=%d\n”, i, a[i]);
 }
}

この配列を動的に割り当てると以下のようになります。

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

void main() {
  int *a;
  int i;

  a = (int *)malloc(sizeof(int) * 3);

  a[0] = 1;
  a[1] = 2;
  a[2] = 4;

  for ( i = 0; i < 3; i++)
   printf(“a[%d]=%d\n”, i, a[i]);

  free(a);
}

ここで sizeof演算子が登場します。これは型の名前を引き数に書くことで、その型を仕舞うのに必要なメモリの大きさをバイト数で返します。例ではint型を3個使いたいので、int型に必要な大きさの3倍のバイト数をmallocに渡しています。これで安心してint [3]に相当するメモリを使うことができます。

mallocが返すポインタには、どの型のメモリであるかの情報が含まれていない(void*という型)ので、必ず代入する変数の型に明示的に変換する必要があります。変換は変数宣言と同じ型を()で囲んで’=’の直後に書けばOKです。

最後にメモリを返すfreeには、mallocで代入した変数を渡します。システムは何バイトのメモリを確保したかを記憶しているので、同じ値のポインタ(コピーしたポインタでもOK)であれば長さを渡す必要はありません(こちらはどんなポインタ型でも変換せずに書いてOK)。

基本的な使い方がわかったところで、文字列のコピーを動的なメモリを使って書いてみましょう。

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

void main() {
  char s[] = "abcdefg";
  char *d;
  char *p, *q;
  int i = 0;

  for (p = s; *p != '\0'; p++)
    i++;

  d = (char*)malloc(i);

  p = d;
  q = s;
  while ((*p++ = *q++) != '\0')
    ;

  printf("s=%s\n", s);
  printf("d=%s\n", d);

  free(d);
}

ちゃんとコピーできましたか?

s=abcdefg
d=abcdefg

以前に文字列のコピーは説明したので大丈夫ですね。コピー元の文字列の長さを数えて、その大きさのメモリを動的に確保してから、そのメモリに文字列をコピーしています。コピーした文字列を表示すれば、もう不要なので最後にメモリを返してからプログラムを終えます(表示する前に返しては駄目ですよ)。

補足
char *p, *q; とchar型の変数を2つ宣言していますが、ここで char* p,q; としないように注意しください。この書き方だと char *p と char q が宣言されたことになります。宣言で使う’*’は型名の一部なのですが、カンマで区切った場合、’*’は変数毎に書かないといけないんです。

malloc の引き数で渡す数字ですが、char 型の大きさは1バイトであることはC言語で共通なので、わざわざ sizeof(char) とやらずに、数えた長さをそのまま渡しています。本当なら d = (char*)malloc(sizeof(char)*i) が正しいですが、手を抜くことが殆です。

課題

引き数で渡された文字列を、関数の中で動的に確保したメモリに文字列をコピーし、(引き数で渡した方ではなくて)コピーした文字列の先頭を指すポインタを返す関数を書いてください。

前回の解答は 

C言語教室 番外編3 - 第6回の課題について

を見てください。


参考:【C言語】malloc関数(メモリの動的確保)について分かりやすく解説

ヘッダ画像は以下のものを使わせて頂きました。
https://www.irasutoya.com/2014/06/blog-post_6564.html


この記事が気に入ったらサポートをしてみませんか?