C言語を使っているとC++のstring/vectorが使えないせいで
可変長の文字列を含んだファイルを読込むときは
非常に泥臭いことをしないといけない。。。か、もしくは
決めうちで列幅を固定してしまったりすることが多いと思います。
そんなわけでどんなに列幅があってもどんなに行数があっても
簡単に読込める関数を作りました。勉強を兼ねて。
作るところで、簡単にできるだろうと目論んでいましたが、
やってみると盛大にバグりました。
細かいミスがすごい出ました。しかも不具合の原因がすぐにわからない。
久々にC言語のキツさを思い知りました。
低レイヤーの言語って泥臭いけど楽しいですよね。
使い方
こんな感じです。
2次元配列的な構造を列幅、行数を意識せずに取得できるようにしたものです。
もちろん、ヘッダに分離させてソースコードを置いたらincludeとかしないとダメですが。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define SAFE_RELEASE(p) if(p) { free(p); p=NULL; }
#define DEFAULT_SIZE 3
typedef struct Tag_LineData LineData;
typedef struct Tag_StringList StringList;
StringList *getStringList(const char *fileName);
void deallocateStringList(StringList *stringList);
typedef struct Tag_LineData {
char *line;
size_t length;
} LineData;
typedef struct Tag_StringList {
LineData *lineData;
size_t length;
} StringList;
static char *getLineDataRecursive(LineData *lineData, FILE *fp, int depth) {
char *tmp = (char *)realloc(lineData->line, DEFAULT_SIZE * depth * sizeof(char));
if (tmp == NULL) {
perror("realloc");
SAFE_RELEASE(lineData->line);
exit(1);
}
if (depth == 1) { memset(tmp, 0, DEFAULT_SIZE); }
lineData->line = tmp;
char buf[DEFAULT_SIZE] = { 0 };
char *ret = fgets(buf, DEFAULT_SIZE - 1, fp);
if (ferror(fp)) {
perror("read failed");
exit(1);
}
strcat(lineData->line, buf);
if (strchr(lineData->line, '\n') != NULL) {
lineData->length = strlen(lineData->line);
lineData->line[lineData->length - 1] = '\0';
return ret;
}
#if _DEBUG
printf("recursive call===\n");
#endif
return ret != NULL ? getLineDataRecursive(lineData, fp, ++depth) : ret;
}
static char *getLineData(LineData *lineData, FILE *fp) {
return getLineDataRecursive(lineData, fp, 1);
}
static StringList* allocateStringList(int lineCnt, FILE* fp) {
StringList *stringList = (StringList *)malloc(lineCnt * sizeof(StringList));
if (stringList == NULL) {
perror("malloc");
exit(1);
}
stringList->length = lineCnt;
for (int i = 0; i < lineCnt; i++) {
stringList[i].lineData = (LineData *)malloc(sizeof(LineData));
if (stringList[i].lineData == NULL) {
perror("malloc");
exit(1);
}
}
return stringList;
}
static int getLineCnt(FILE* fp) {
int lineCnt = 0;
LineData lineData = { 0 };
while (getLineData(&lineData, fp) != NULL) {
SAFE_RELEASE(lineData.line);
lineCnt++;
}
rewind(fp);
return lineCnt;
}
StringList *getStringList(const char *fileName) {
assert(DEFAULT_SIZE > 2);
FILE* fp = fopen(fileName, "r");
if (fp == NULL) {
perror("fopen");
exit(1);
}
int lineCnt = getLineCnt(fp);
printf("lineCnt: %d\n", lineCnt);
StringList* stringList = allocateStringList(lineCnt, fp);
for (int i = 0; i < lineCnt; i++) {
getLineData(stringList[i].lineData, fp);
}
fclose(fp);
return stringList;
}
void deallocateStringList(StringList *stringList) {
for (int i = 0; i < stringList->length; i++) {
SAFE_RELEASE(stringList[i].lineData->line);
SAFE_RELEASE(stringList[i].lineData);
}
SAFE_RELEASE(stringList);
}
int main(void) {
StringList *stringList = getStringList("test.txt");
for (int i = 0; i < stringList->length; i++) {
printf("%s\n", stringList[i].lineData->line);
}
deallocateStringList(stringList);
return 0;
}
追記
この記事を書いた後、別のコード例を紹介してくださった記事がありました。
ソースコードを見ると綺麗で無駄がなく、ヘンテコな構造体を定義していなく
普通の用途で使うならこれがいいのでは?
という素晴らしいコードでした。解説もわかりやすいです!