いいね、作るよ — 「C言語のファイル操作 完全入門」的にまとめて、実用的なコード例(テキスト/バイナリ/構造体)と注意点を一気に出します。読みたいところからサッと使ってください。
基本:ファイル操作でよく使う関数
FILE *fopen(const char *path, const char *mode); — ファイルを開く。失敗すると NULL。
int fclose(FILE *stream); — ファイルを閉じる。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); — バイナリ読み込み(items数を返す)。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); — バイナリ書き込み(items数を返す)。
int fprintf(FILE *stream, const char *format, ...); / int fscanf(FILE *stream, const char *format, ...); — 形式付き(テキスト)入出力。
char *fgets(char *s, int size, FILE *stream); — 行読み(バッファ長に注意)。
int fputs(const char *s, FILE *stream); / int fgetc(FILE *stream); / int fputc(int c, FILE *stream); — 文字列/文字入出力。
int fseek(FILE *stream, long offset, int whence); / long ftell(FILE *stream); / void rewind(FILE *stream); — ファイル位置制御。
int fflush(FILE *stream); — バッファ内容をフラッシュ(強制書き出し)。
必要ヘッダ:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h> // 固定幅整数 (uint32_t など)
#include <errno.h>
fopen のモード早見表
"r" : 読み込み(ファイル必須)
"w" : 書き込み(新規作成、既存は上書き・切り捨て)
"a" : 追記モード(ファイルがなければ作成)
"r+" : 読み書き(ファイル必須、先頭)
"w+" : 読み書き(作成して上書き)
"a+" : 読み書き(追記。書き込みは原則末尾)
... + "b" : バイナリモード(Windowsで改行変換を防ぐ)。例: "rb", "wb", "r+b"。
基本サンプル:テキスト書き込み/読み込み
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("out.txt", "w");
if (!fp) { perror("fopen"); return 1; }
fprintf(fp, "Hello, %s! %d\n", "world", 2025);
fclose(fp);
fp = fopen("out.txt", "r");
if (!fp) { perror("fopen"); return 1; }
char buf128;
while (fgets(buf, sizeof buf, fp)) {
printf("line: %s", buf);
}
fclose(fp);
return 0;
}
ポイント:fgets はバッファオーバーフローを避けられるので fscanf より安全。fscanf はフォーマット指定ミスで危険になりやすい。
バイナリ書き込み(raw bytes)と fwrite / fread
fwrite と fread は「要素数(nmemb)」で成功判定します。たとえば3バイト書くなら fwrite(buf, 1, 3, fp)、3個の構造体を書きたいなら fwrite(arr, sizeof *arr, 3, fp)。
書き込みチェック例:
size_t written = fwrite(buf, 1, buflen, fp);
if (written != buflen) {
// エラー処理 (disk full 等)
perror("fwrite");
}
構造体をファイルに保存する — 危険点と実用例
直接 fwrite(&s, sizeof s, 1, fp) で構造体を丸ごと保存するのは簡単ですが、注意点が多い:
パディング(隙間):コンパイラが構造体にパディングを入れるのでバイト列が環境依存。
エンディアン:整数のバイト順がプラットフォームで異なる(LE/BE)。
ポインタ:ポインタの値を書いても意味がない(プロセス間で無効)。
型サイズ:int のサイズが環境で違う。uint32_t 等を使う方がマシ。
単純な(同一環境向け)例
#include <stdio.h>
#include <stdint.h>
typedef struct {
char name32;
uint32_t age;
double height;
} Person;
int main(void) {
Person p = { "Alice", 30, 1.62 };
FILE *fp = fopen("people.bin", "wb");
if (!fp) { perror("fopen"); return 1; }
if (fwrite(&p, sizeof p, 1, fp) != 1) { perror("fwrite"); }
fclose(fp);
// 読み戻し
fp = fopen("people.bin", "rb");
Person q;
fread(&q, sizeof q, 1, fp); // 要チェック
fclose(fp);
printf("%s %u %.2f\n", q.name, q.age, q.height);
return 0;
}
この方法は同じコンパイラ・設定・アーキテクチャでのデータ保存には便利。
ポータブルにしたい/長期保存するなら(推奨)
テキストで保存:fprintf でフィールドを書いて fgets+sscanf 等で読み取る(可視・トラブル解決しやすい)。
明示的にバイナリ表現を決める:固定幅整数(uint32_t 等)を使い、整数はネットワークバイトオーダ(Big Endian)で保存する(htonl / ntohl)。浮動小数点は規格依存なので注意。
長さを先に書く:可変長文字列は先に長さを書き、その後データ本体を書く(読み込み時にバッファ確保可能)。
ポータブルなバイナリ例(文字列長を明示):
// 書き込み (擬似)
uint32_t name_len = (uint32_t)strlen(name);
uint32_t name_len_be = htonl(name_len);
fwrite(&name_len_be, sizeof name_len_be, 1, fp);
fwrite(name, 1, name_len, fp);
uint32_t age_be = htonl(age);
fwrite(&age_be, sizeof age_be, 1, fp);
読み込み時は逆に fread → ntohl → malloc して fread。
レコード単位での更新(例:n番目の構造体を書き換える)
ファイルを "r+b"(読み書き)で開く。
fseek(fp, index * sizeof(Person), SEEK_SET); で位置を移動して fwrite する。
FILE *fp = fopen("people.bin", "r+b");
fseek(fp, index * sizeof(Person), SEEK_SET);
fwrite(&new_person, sizeof new_person, 1, fp);
fclose(fp);
注意:構造体丸ごと更新は上記のポータビリティ問題を受け継ぐ。
ファイルサイズを得る(簡便法)
fseek(fp, 0, SEEK_END);
long size = ftell(fp); // 大きいファイルは off_t を使う環境もある
rewind(fp); // または fseek(fp, 0, SEEK_SET)
エラーハンドリング(重要)
fopen が NULL → perror/errno を確認。
fread / fwrite は返り値(要素数)を必ず確認:size_t n = fread(buf, size, nmemb, fp); if (n != nmemb) { if (feof(fp)) ... else if (ferror(fp)) ... }
feof(fp) と ferror(fp) で EOF と I/O エラーを判定。
fflush(fp) は fclose でも自動フラッシュされるが、途中で確実に書き出したい場合に呼ぶ。永続性を完全に保証したければ POSIX の fsync(ファイルディスクリプタが必要)を使う。
安全にファイルを書き換える(途中で失敗させない)
新しい内容を一時ファイルに書く(例 "data.tmp")。
fflush + fclose。
rename("data.tmp", "data.bin") で置き換え(POSIXでは atomic)。
これで途中でクラッシュしても元ファイルは残ります。
文字列や可変長データの扱い方(実用)
可変長文字列をバイナリで保存するなら「長さ(固定幅)+データ本体」がおすすめ。
テキスト保存なら fprintf(fp, "%s\t%u\t%.2f\n", name, age, height) で行単位にしておくと diff も使えるしデバッグ容易。
ファイルロック(競合回避)
単一プロセスなら気にしなくてOK。複数プロセス/スレッドで同ファイルを扱うなら flock()(POSIX)や fcntl()、Windows の LockFileEx 等が必要。簡単な共有時はロックを検討。
まとめ:よくあるケース別アドバイス
ログや簡単なテキスト出力 → fprintf / fputs / "a"(追記)でOK。
バイナリ高速保存(同環境でしか読まない) → fwrite / fread で構造体丸ごと書く(注意:ポータビリティ)。
長期保存・異なる環境で読む可能性あり → テキスト保存か、フィールド単位でバイナリ化(固定幅整数+バイトオーダ統一)してシリアライズ。
部分更新/レコード管理 → 固定長レコードが扱いやすい(fseek で直接アクセス)。可変長だと索引(offset表)を用意する必要あり。
実践的な「構造体をテキストで保存」サンプル
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
char name32;
uint32_t age;
double height;
} Person;
int main(void) {
Person p = {"Bob", 28, 1.75};
FILE *fp = fopen("people.txt", "a"); // 追記テキスト
if (!fp) { perror("fopen"); return 1; }
fprintf(fp, "%s\t%u\t%.6f\n", p.name, p.age, p.height);
fclose(fp);
// 読み込み例
fp = fopen("people.txt", "r");
if (!fp) { perror("fopen"); return 1; }
char line256;
while (fgets(line, sizeof line, fp)) {
Person r;
if (sscanf(line, "%31s %u %lf", r.name, &r.age, &r.height) == 3) {
printf("name=%s age=%u height=%.2f\n", r.name, r.age, r.height);
}
}
fclose(fp);
return 0;
}
必要なら次にこれをやります(選べるサンプル):
「構造体をバイナリで安全に(エンディアン対応)保存/読み込み」フル例
「可変長文字列を含むデータのシリアライズ(長さ+本文)」の実装例
ファイルロック(flock)を使った並行アクセスの例
POSIX の低レベル(open, read, write, lseek)による実装