fgetsでファイルを読み込むためのバッファの大きさ設定方法

C言語初心者向けメモ.
僕はファイル処理をする場合にはfgetsを多用するのだけれど,その際

#define BUFFLEN 102400
char buff[ BUFFLEN ];

とか,スタック領域に乗らない程大きな領域であれば

char *buff = malloc(sizeof(char) * BUFFLEN);

というようにゆとり領域を用意してしまう.


fgetsで一行ずつ処理を行いたい場合には,上記buffの大きさはファイルにおける最大幅あれば十分.ただし,最大幅を知るためには一度ファイルを走査する必要がある.というわけでファイルの行あたりの最大文字幅を決めてから,バッファを用意してfgetsでファイルを読み込むコードを書いてみた.

その際に「あ,これはハマりそうだな」という点に気がついたのでメモ.


こんな感じでfgetcを使って幅を調べる.

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

int
main (int argc, char *argv[])
{
  if (argc < 2) {
    fprintf("Input filename.\n");
    exit(1);
  }

  FILE *fp = fopen(argv[1], "r");
  if (fp == NULL) {
    perror("fopen");
    exit(1);
  }

  /* 最大の長さの行とその文字数を数える */
  char ch;
  int chlen = 0;
  int chmax = 0;
  while ((ch = fgetc(fp)) != EOF) {
    chlen++;
    if (ch == '\n') {
      if (chlen > chmax) {
        chmax = chlen;           /* 間違い */
        // chmax = chlen + 1; /* こちらが正しい */
      }
      chlen = 0;
    }
  }
  printf("chmax=%d\n", chmax);
  
  /* 巻き戻し */
  fseek(fp, 0, SEEK_SET);

  /* fgets()で読み込み */
  char buff[ chmax ];
  while (fgets(buff, chmax, fp) != NULL) {
    printf("%s", buff);

    /* NULL文字を印字 */
    if (buff[ strlen(buff) ] == '\0') {
      printf("## NULL HERE ##\n");
    }
  }

  fclose(fp);
  return 0;
}

間違いと書いている部分に落とし穴がある.行頭から改行文字を含む文字数をカウントしているのだけれど,それをバッファの大きさにしてしまうとヌル文字を格納することができない.そのため,fgetsで最後の一文字を「次回に回してしまう」.
上記コードを実行するとわかりやすい.

サンプルテキストはこんな感じ

% cat sample.txt
Learn from yesterday, live for today, hope for tomorrow.
The important thing is not to stop questioning.
-- Albert Einsten

実行結果

% ./a.out sample.txt
chmax=57
Learn from yesterday, live for today, hope for tomorrow.## NULL HERE ##

## NULL HERE ##
The important thing is not to stop questioning.
## NULL HERE ##
-- Albert Einsten
## NULL HERE ##

## NULL HERE ##

最初の一行が最大幅のため57というサイズがbuffを確保する大きさに用いられている.けれど,ヌル文字まで含めた文字列としての大きさは58必要なため,最後の改行文字を諦めて,次のfgets呼び出しに回していることが確認できる.


これを改善するためには,/* こちらが正しい */と書いてあるようにヌル文字用に1大きめに確保しなければならない.

% ./a.out sample.txt
chmax=58
Learn from yesterday, live for today, hope for tomorrow.
## NULL HERE ##
The important thing is not to stop questioning.
## NULL HERE ##
-- Albert Einsten
## NULL HERE ##

## NULL HERE ##

これで正しくfgetsが動くようになった.