fgetcで書ければ、freadで書ける
初心者では、fgetcで一文字づつ処理するコードは記述できるのに、バッファを使うfreadで書くのに悩む人もいます。
単純にfgetcで一文字づつ処理するコードは、以下のような形になるでしょう
for (;;) { int ch = fgetc(file); if (ch == EOF) break; ... }
これをバッファとfreadを使う形式に変換すると以下のようになるだけです(buf, i, countはかぶらない前提で):
char buf[BUF_SIZE]; for (;;) { int i; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; ... } if (count < BUF_SIZE) break; }
(追記: fgetc,freadともに、終了判定条件は、feof(file) のほうがよいかもしれない。)
できるかぎり単純化したもので考えれば、freadを使うということとfgetcを使うのとに大差はないのがわかるのではないでしょうか。
同様な、文字単位読み込み⇔バッファ単位読み込みは、Javaのio⇔nioでもおきますが、これも同様にそんなに概念差があるものではないです。
ふと思ったのは、fgetcを使うとき最初にwhile ( (ch = fgetc(file) ) != EOF) {...}と覚えさせてしまうことが、freadの壁をより大きくしてるかもしれないな、と。
例: readlines
標準の関数(stdlib/stdio)だけを使って、ファイル全体を読み込んで、文字列の配列(配列の最後はNULL)を返すchar ** readlines(FILE*)関数の実装を考えてみましょう。
この関数は、擬似コードで書くと以下のようになります:
char ** readlines(FILE * file) { char ** lines; int lineindex = 0; int index = 0; linesバッファの確保; for (;;) { int ch = fgetc(file); if (ch == EOF) break; if (ch == CRの直後のLF) continue; linesバッファの拡張; lines[lineindex]バッファの確保; lines[lineindex]バッファの拡張; if (ch == CR || ch == LF) { lineindex++; index = 0; } else { lines[lineindex][index] = ch; index++; } } return lines; }
実際のコードでは定数定義、バッファの確保や進展、収縮、最後に0を入れる等をすることになるので長くなりますが、そのあたりを省略してあります。
実際のCコードでは、以下のようになります
char ** readlines(FILE * file) { enum {LINE_SIZE = 80}; enum {LINES_SIZE = 25}; enum {CR = '\r'}; enum {LF = '\n'}; int lineindex = 0; int lines_size = LINES_SIZE; char ** lines = NULL; int index = 0; int line_size = 0; int is_prev_cr = 0; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int ch = fgetc(file); if (ch == EOF) break; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) continue; } if (lineindex + 1 == lines_size) { char ** expanded; lines_size *= 2; expanded = realloc(lines, lines_size * sizeof(char *)); if (expanded == NULL) goto error; lines = expanded; } if (lines[lineindex] == NULL) { line_size = LINE_SIZE; lines[lineindex] = malloc(line_size * sizeof(char)); if (lines[lineindex] == NULL) goto error; lines[lineindex + 1] = NULL; lines[lineindex][0] = '\0'; } if (index + 1 == line_size) { char * expanded; line_size *= 2; expanded = realloc(lines[lineindex], line_size * sizeof(char)); if (expanded == NULL) goto error; lines[lineindex] = expanded; } if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { realloc(lines[lineindex], (index + 1) * sizeof(char)); lineindex++; index = 0; } else { lines[lineindex][index] = (char) ch; index++; lines[lineindex][index] = '\0'; } } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
これをfreadを使うようにすると、機械的に以下のように変更できます:
char ** readlines(FILE * file) { enum {LINES_SIZE = 32}; enum {LINE_SIZE = 128}; enum {BUF_SIZE = 1024}; enum {CR = '\r'}; enum {LF = '\n'}; int lineindex = 0; int lines_size = LINES_SIZE; char ** lines = NULL; int index = 0; int line_size = 0; int is_prev_cr = 0; char buf[BUF_SIZE]; assert(file != NULL); lines = malloc(lines_size * sizeof(char *)); if (lines == NULL) goto error; lines[0] = NULL; for (;;) { int i = 0; int count = fread(buf, sizeof(char), BUF_SIZE, file); for (i = 0; i < count; i++) { char ch = buf[i]; if (is_prev_cr) { is_prev_cr = 0; if (ch == LF) continue; } if (lineindex + 1 == lines_size) { char ** expanded; lines_size *= 2; expanded = realloc(lines, lines_size * sizeof(char *)); if (expanded == NULL) goto error; lines = expanded; } if (lines[lineindex] == NULL) { line_size = LINE_SIZE; lines[lineindex] = malloc(line_size * sizeof(char)); if (lines[lineindex] == NULL) goto error; lines[lineindex + 1] = NULL; lines[lineindex][0] = '\0'; } if (index + 1 == line_size) { char * expanded; line_size *= 2; expanded = realloc(lines[lineindex], line_size * sizeof(char)); if (expanded == NULL) goto error; lines[lineindex] = expanded; } if (ch == CR) is_prev_cr = 1; if (ch == CR || ch == LF) { realloc(lines[lineindex], (index + 1) * sizeof(char)); lineindex++; index = 0; } else { lines[lineindex][index] = (char) ch; index++; lines[lineindex][index] = '\0'; } } if (feof(file)) break; } realloc(lines, (lineindex + 2) * sizeof(char *)); return lines; error: freelines(lines); return NULL; }
ここから、たとえばデータコピーで最適化したり、直接読み込ませたりすることを考えることができたりしそうです。
ちなみにfreelinesは以下のコードです
void freelines(char ** lines) { char ** cursor; if (lines == NULL) return; for (cursor = lines; *cursor; ++cursor) free(*cursor); free(lines); }