見出し画像

続C言語教室 - 第9回 行を単位としない読み書き

ファイルからデータを読んだり書いたりする場合、対象がテキストファイルであれば改行コードで区切られた「行」を単位とする処理を書くことが多いのですが、そのテキストの内容は必ずしも行に意味があるかと言えばケースバイケースです。

CSVファイルであったり、コマンドが並べられているファイルであれば、行には意味があり行を単位として処理をするのは正しいのですが、いわゆる本当のテキスト、つまり文章が並んでいるファイルであれば改行は表示上の区切りであったり段落区切りであって、必ずしも行を単位として処理を行うことが必要ではありません。読み込むバッファサイズの都合で行を分割して処理をすることがあるのであれば、最初から決まった文字数(バイト数)を読み書きしたほうがわかりやすい処理を書くことが出来ます。

そのような目的の場合に使われるのが fread および fwrite です。

#include <stdio.h>
size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);

fread のプロトタイプ

#include <stdio.h>
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);

fwrite のプロトタイプ

fread

fwrite

これらのライブラリ関数は「バイナリファイル」に対して使うものであるかのような説明があるのですが、いずれもストリームファイル入出力の関数で、バッファリングもされますし、fopen のモード指定でバイナリモードを指定しなければ改行コードの置き換えも行われます(そもそもバイナリモードがあれば)。単に文字単位でも改行コード区切りでもなくバイト数を基準に読み書きするだけです。

fread

fwrite

最近はバッファサイズの管理が厳格で、どうせ指定したサイズ(を最大として)で読み書きするのですし、そもそも改行コードに何が使われているのかを当てに出来ない現実もあります。ですから行に関する処理はライブラリ側に任せずに自分でコントロールした方が、むしろ楽ですし安心です。

#include <stdio.h>
// VSの時にfopenの警告が出るので対策
#pragma warning(disable:4996)

void main() {
#define BUFLEN (15)

  char s[BUFLEN] = "Hello World.\n";
  char t[BUFLEN] = "";
  FILE* fp = NULL;

  fp = fopen("sample.txt", "w");
  if (fp != NULL) {
	int len = fwrite(s, sizeof(char), BUFLEN, fp);
	printf("write length=%d\n", len);
	fclose(fp);
  } else {
	printf("ERROR in fopen() for write.\n");
	return;
  }

  fp = fopen("sample.txt", "r");
  if (fp != NULL) {
	int len = fread(t, sizeof(char), BUFLEN, fp);
	printf("read length=%d\n", len);
	for (int i = 0; i < BUFLEN; i++) printf("%02x ", t[i]);
	printf("\n");
	fclose(fp);
  } else {
	printf("ERROR in fopen() for read.\n");
	return;
  }
}

このコードを走らせると sample.txt というファイルを作って"Hello World.\n" を書き込んで読み取り内容を16進数で出力します。

write length=15
read length=15
48 65 6c 6c 6f 20 57 6f 72 6c 64 2e 0a 00 00

バッファサイズは文字列リテラルより長く15バイト分あり、余分なバイトは'\0'で埋められます(C言語の正しい動作)。これを見る限りコードで書いたとおりに動作しています。ところがファイルサイズを確認してみると16バイトとなっています。ファイルの内容をダンプすると(一部省略)、

PS>format-hex -path "./sample.txt"

00000000   48 65 6C 6C 6F 20 57 6F 72 6C 64 2E 0D 0A 00 00  Hello World..…

となっており改行コードの変換が行われていることがわかります。fopenでバイナリモードを指定していないからですね。ライブラリ関数の引き数を見ても、本来は配列のような同じサイズのデータが並んでいる時に使うものなのですが固定したサイズのバッファでテキストを読み書きするのに使っても問題ありません。ただ末端の処理をきちんとやらないと、この例のように末尾に余分なヌル文字をつけてしまうので、そこはキチンと書きましょう^^;。

C言語 fread/fwrite【バイナリファイルの書き込み・読み込み】

初心者でも安心!C言語でバイナリファイルを読み込む7つのステップ

これで読む時は決まったサイズだけ読み取り、行なり区切り文字を見つけたらそこまでの処理を行って、バッファに残ったデータを先頭に移動し、その後ろのポインタを渡して残りのサイズで続きを読むなんて言う書き方を良くします。読む処理と書く処理は対称的なコードが良いような気もしますが、書く時は fprintf などで改行コードの処理はライブラリにお任せという使い方で済ませてしまうことが殆どです。読む時はおおらかに、書く時は決まった通り厳密に、というのが私のポリシーです。

さて、ストリーム入出力の細かな話をしたら、本当のバイナリ読み書きに進みますか(同時に開かれたファイルの話はその後で)。

ヘッダ画像は、AdobeExpressのAIに描いてもらいました。

#C言語 #プログラミング #プログラミング講座 #ファイル入出力 #ストリーム #fread #fwrite #バイナリモード #テキストモード #改行コード



この記事が気に入ったらサポートをしてみませんか?