C言語 - テキストファイルを全部、一行ずつ、無駄なく(?)配列に格納する

Pythonでいうと、やりたいことはこれ。

lines = open("text").readlines()

C言語では、一行が100001バイト以上あるファイルじゃなければ、とりあえずこれでよかった。

char* *lines;
int lines_length;

void load_file(char *file_path) {
  FILE *fp;
  int i;
  long bufsize = 100000;
  char buf[bufsize];
  fp = fopen(file_path, "r");
  if (fp == NULL) {
    printf("can not open %s.\n", file_path);
    exit(1);
  }
  for(i=0; fgets(buf, bufsize, fp)!=NULL; i++);
  rewind(fp);
  lines = malloc(sizeof(char*) * i);
  for(i=0; fgets(buf, bufsize, fp)!=NULL; i++) {
    lines[i] = malloc((sizeof(char) * strlen(buf)) + 1);
    strcpy(lines[i], buf);
  }
  lines_length = i;
  fclose(fp);
}

共有オブジェクト(SO)にコンパイルすれば、このままpythonで利用できる。

import ctypes

ext = ctypes.CDLL("./ext.so")
ext.load_file("file_path")

SOのコンパイル方法

gcc バージョン 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
gcc -Wall -fPIC -c ext.c
gcc -shared -o ext.so ext.o

試しに約50MB、約5万行のファイルを読んでみたところ、topでの観測では使用メモリが約52MB増えた。
この2MBは何だろう。

  • 各行へのポインタ、64bit環境なので(8 * 50000 / 1024.0 = 390KB)
  • strcpy()が返す終端(\0)のための領域、char一つ分なので(1 * 50000 / 1024.0 = 49KB)
  • buf(100000 / 1024.0 = 97KB)
  • あとは何だろう。謎。ls -hやtopで丸めて出来た誤差かも。

linesはfree()しないとプロセスが終了するまでメモリを占有し続けることに注意。
必要なければ、linesはローカルに置いてもいいと思う。
今回は、どうしてもlinesを別の関数で読みたくて、グローバルに置いて、free()せずに放置している。

「"文字列の配列" malloc」とか検索しても、ずばり!というページがなかなかヒットしなかった。

しかし長い!もっといい仕方がないかな。