現在までのプログラムでは、変数はすでにプログラミングの段階で大きさが決定されていた。 しかしながら、様々な場面で用いられるプログラムの場合、扱うデータの量はあらかじめ予想できない事が多い。 (たとえば、課題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
関数の書式は次のようなものである。
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
関数は次のような書式である。
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; }
与えられたファイルに含まれる各行を検査し、もっとも長い行に関する情報を 表示するプログラムを作成せよ。 表示する情報としては、行番号、長さおよび、その行そのものの3つとする。 ただし、一行の長さに関する制限はないものとする。
11日目 | 表紙 | 13日目 |