11日目 13日目

12日目

目標

課題 14

メモリの動的確保/解放

現在までのプログラムでは、変数はすでにプログラミングの段階で大きさが決定されていた。 しかしながら、様々な場面で用いられるプログラムの場合、扱うデータの量はあらかじめ予想できない事が多い。 (たとえば、課題13で取り上げた学生の成績をつけるプログラムを例にとると、 選択科目であれば、受講生の数は開講初日にふたを開けてみないと判らない。)

学生の成績をつけるプログラムの場合には、最大数の予測をつけることもできるが、 一般の場合には予測が困難な場合も考えられるし、最大の場合に備えた巨大な配列を 用意することは資源(メモリ)の無駄遣いでもある。 あるいは配列が大きすぎて実行できなくなる場合も考えられる。 このような場合の最適な解決方法の一つは、必要な量のメモリを必要となった場合に用意するという戦略である。

メモリの確保

必要なときに必要なだけのメモリを確保する方法として、C では、malloc()関数を用いることができる。 (malloc()関数は stdlib.hで定義されているので、#include <stdlib.h>が必要である。) malloc 関数は確保したメモリの先頭アドレスを返すので、ポインタ変数を用いてそのメモリ領域を管理する。

  #include <stdlib.h>
  ...

  char *s;
  s = malloc(512);

上の例では 512バイトの領域を確保し、その先頭アドレスをポインタ変数 s に代入している。

  #include <stdlib.h>
  ...

  int *s;
  s = malloc(sizeof(int)*50);

上の例では、int 型の変数50個分の領域を確保し、その先頭アドレスをポインタ変数 s に代入している。

メモリが確保できなかった場合、malloc は、NULLを返すので、正しくメモリが確保できたかどうかを 確認することができる。当然ながら確保できなかった場合は、プログラムは期待通りの動きはしないのでエラーチェックを入れる必要がある。

確保されたメモリは、初期化を行ってから使用すること。

メモリの解放

malloc関数で確保されたメモリは不要になっても、そのままでは解放されないため システムで利用可能なメモリが少なくなっていく。 (自動的に確保された変数はスコープを抜けると解放されていたのであった。) このため、malloc で確保した領域が不要になった場合、システムへ返却する必要がある。 システムの返却には free関数を用いる。free 関数はポインタを 引数に取り、そのポインタが指すメモリ空間を解放する。ただし、このポインタは 以前に呼び出された malloc関数(および、その仲間)が返した値でなければ ならないことに注意する。 (従って、途中で確保したメモリを解放したい場合には malloc 関数が返した値を保持する 変数と、その領域を操作するための変数の2つを利用することになる。)


fgets関数

ストリームから読み込む関数として、これまでに取り上げたものに加えて良く使うものとして fgets関数がある。 fgets関数の書式は次のようなものである。

    char *fgets(char *, int, FILE *);

使用例

     char buf[80];
     FILE *fp;
...
     fgets(buf, sizeof(buf), fp);

上の例では、fgets関数は、fpで指定されるストリームから 最大sizeof(buf)(80 である)よりも 1だけ少ないデータを読み込み、 buf に格納する。ただし、EOF(ファイルの終端)もしくは、改行文字を 読み込んだ時点で終了する。読み込んだ文字の後ろには '\0' 文字が付け加えられ、 buf は文字列として正しいことが保証される。

従って、fgetsは、行単位の処理を行いたい場合等に良く用いられる関数である。 行全体が読み込まれたかどうかは、終端文字の1つ前が改行かどうかで判断することが可能である。 あるいは、strchr関数を用いて、buf中に '\n' が含まれているかどうかで 判断しても良い。ただし、その場合には、最終行に改行文字が含まれていなかった場合の処理を考慮する必要がある。

strchr関数

上でのべた strchr 関数は次のような書式である。

char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);

使用例

#include <string.h>

...
char *p;
...
p = strchr(buf, '5');
if (p == NULL) {
	/* 文字 '5' が見つかった「場所」が p である。*/
	*p = '6';
}

上の例では、文字列 buf を先頭から走査し、最初に出現した文字 '5' を '6'に置き換えている。 文字列の終端から探索をしたい場合には strrchr関数を用いる。


実際に動くような例題として、与えられたファイル("-" の場合は標準入力)の 各行の内容を反対にして出力するプログラムを以下に示す。

/* reverse.c */
/*
 * メモリの動的確保と解放の例題
 */

/* 与えられたファイルの各行を引っくり返す */

#include <stdio.h>
#include <malloc.h>
#include <string.h>

int main(int argc, char *argv[])
{
	FILE *fp;
	char buf[80];
	char *result, *ptr;
	int i;

	if (argc != 2) {
		fprintf(stderr, "使い方: reverse ファイル名\n");
		exit(1);
	}

	if (strcmp(argv[1], "-") == 0) {
		/* ファイル名が - の場合は標準入力を処理する */
		fp = stdin;
	} else {
		if (NULL == (fp = fopen(argv[1], "r"))) {
			fprintf(stderr, "ファイル %s が開けません\n", argv[1]);
			exit(1);
		}
	}


	while (NULL != fgets(buf, sizeof(buf), fp)) {

		/* 改行文字を探す */
		ptr = strchr(buf, '\n');
		if (ptr != NULL) {
			/* 見つかったので、改行文字を削除 */
			*ptr = '\0';
		}

		/* データの長さ + 1 だけのメモリを確保を要求 */
		result = malloc(strlen(buf) + 1);

		/* 確保できたかどうかの確認 */
		if (NULL == result) {
			/* メモリが確保できなかった */
			fprintf(stderr, "メモリが確保できませんでした。\n");
			exit(1);
		}

		ptr = result;  /* 作業用ポインタ として使う */
		for (i = strlen(buf) - 1; i >= 0; --i, ptr++) {
			/* i は後ろから、ptr は前から動いている */
			*ptr = buf[i];
		}
		*ptr = '\0';
		printf("%s\n", result);

		/* 不要になったメモリを解放する。
		 * result の内容は malloc したときから
		 * 変更されていないことに注意
		 */
		free(result);
	}
	return 0;
}

問題 14

与えられたファイルに含まれる各行を検査し、もっとも長い行に関する情報を 表示するプログラムを作成せよ。 表示する情報としては、行番号、長さおよび、その行そのものの3つとする。 ただし、一行の長さに関する制限はないものとする。


11日目 表紙 13日目

tacha@tack.fukui-med.ac.jp
$Id$