標準 I/O は、C 標準ライブラリに含まれる高レベルの入出力抽象化層であり、内部的にはシステムコール(低レベル I/O)をラップして提供されます。これにより、ファイルやストリームの操作が直感的かつ柔軟に可能となり、バッファリングやフォーマット指定による効率的なデータ処理が実現されます。
FILE 構造体と内部実装概要
標準 I/O の基本単位は FILE 型で、これは実際には内部構造体 _IO_FILE のエイリアスです。これにはバッファ管理用ポインタやファイル記述子(_fileno)が含まれ、アプリケーション側で透過的に利用されます。
typedef struct _IO_FILE FILE;
struct _IO_FILE {
int _flags;
char *_IO_read_ptr, *_IO_read_end, *_IO_read_base;
char *_IO_write_base, *_IO_write_ptr, *_IO_write_end;
char *_IO_buf_base, *_IO_buf_end;
// ...(中略)...
int _fileno; // ベースとなるファイル記述子
_IO_lock_t *_lock; // スレッドセーフ制御用( ANSI C 未定義)
// ...
};
データ書き込みはまずユーザ空間の I/O バッファに蓄積され、バッファの状態に応じて暗黙的にフラッシュされます。これに対し、システム I/O は書き込み即時フラッシュを基本とします。
ファイル操作
オープン操作は fopen() 関数で行い、失敗時は NULL を返します。
FILE *fopen(const char *path, const char *mode);
モード文字列(例: "r", "w", "rb")により読み書きモードとテキスト・バイナリ切り替えを行います。
クローズ処理は fclose() で行い、ストリームと関連バッファを解放します。
int fclose(FILE *stream);
多重クローズや未オープンファイルへのクローズは未定義動作です。
文字単位 I/O
1 文字ずつの入出力には以下の関数群を使用します。いずれも int を返す点に注意(EOF マークは -1、char に収まらない可能性があるため)。
int fgetc(FILE *stream);— 指定ストリームから 1 文字読み取りint getc(FILE *stream);— マクロ実装(fgetc と同等)int getchar(void);— stdin から 1 文字読み取りint fputc(int c, FILE *stream);— 指定ストリームへ 1 文字書き込みint putc(int c, FILE *stream);— マクロ実装int putchar(int c);— stdout へ 1 文字書き込み
EOF 判別は feof()(終端)、ferror()(エラー)を別途用いて区別します。
ファイルコピー例(文字単位)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *in = fopen(argv[1], "r");
FILE *out = fopen(argv[2], "w");
if (!in || !out) {
perror("fopen");
exit(EXIT_FAILURE);
}
int ch;
long cnt = 0;
while ((ch = fgetc(in)) != EOF) {
fputc(ch, out);
cnt++;
}
if (ferror(in)) {
perror("fgetc");
exit(EXIT_FAILURE);
}
printf("Copied %ld bytes.\n", cnt);
fclose(in);
fclose(out);
return 0;
}
行単位 I/O
1 行ずつ読み書きする関数です。改行を含む最大 size-1 バイトを途中まで読み込み、末尾に null が追加されます。
char *fgets(char *s, int size, FILE *stream);int fputs(const char *s, FILE *stream);
安全のため、古く非推奨かつオーバーフロー危険な gets() は使用すべきではありません。
行単位コピー例(バッファサイズ固定)
#include <stdio.h>
#include <stdlib.h>
#define LINEBUF 256
int copy_lines(const char *src, const char *dst) {
FILE *fp_in = fopen(src, "r");
FILE *fp_out = fopen(dst, "w");
if (!fp_in || !fp_out) {
perror("fopen");
return -1;
}
char buf[LINEBUF];
size_t total = 0;
while (fgets(buf, sizeof(buf), fp_in) != NULL) {
fputs(buf, fp_out);
total += strlen(buf);
}
if (ferror(fp_in)) {
perror("fgets");
fclose(fp_in);
fclose(fp_out);
return -1;
}
printf("Copied %zu bytes.\n", total);
fclose(fp_in);
fclose(fp_out);
return 0;
}
ブロック単位 I/O(バイナリ I/O)
構造体や配列単位で読み書きする場合に用います。型変換やết換コードの影響を受けない pure binary 操作が可能です。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
返り値は実際に転送された「メンバ数」であり、1 つのメンバサイズ(size バイト)×その数が実データ量です。成功時でも短縮読み込みが発生し得るため、終端・エラー判断には feof() / ferror() を併用します。
構造体配列の保存/読み込み例
typedef struct {
char name[32];
int age;
} Person;
void store_people(const char *file, const Person *people, size_t n) {
FILE *fp = fopen(file, "wb");
if (!fp) { perror("fopen"); return; }
size_t written = fwrite(people, sizeof(Person), n, fp);
if (written != n) {
fprintf(stderr, "Write incomplete: %zu/%zu\n", written, n);
}
fclose(fp);
}
Person *load_people(const char *file, size_t *out_n) {
FILE *fp = fopen(file, "rb");
if (!fp) { perror("fopen"); return NULL; }
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
rewind(fp);
size_t n = fsize / sizeof(Person);
Person *ps = malloc(n * sizeof(Person));
size_t loaded = fread(ps, sizeof(Person), n, fp);
if (loaded != n && ferror(fp)) {
perror("fread");
free(ps);
ps = NULL;
}
*out_n = loaded;
fclose(fp);
return ps;
}
ファイル位置操作
FILE ストリームの読み書き位置を操作する関数群です。
int fseek(FILE *stream, long offset, int whence);long ftell(FILE *stream);— 現在位置返却(オフセット)void rewind(FILE *stream);—fseek(stream, 0L, SEEK_SET)に相当int fgetpos(FILE *stream, fpos_t *pos);— 詳細位置を保持(ワイド文字対応)int fsetpos(FILE *stream, const fpos_t *pos);
特にテキストストリームでは offset が意味するバイト数が実際と異なる場合があるため、移植性を重視するなら ftell() + fseek() ペアで位置復元を行うことが推奨されます。
フォーマット付き I/O
入出力において変換指定子("%s", "%d", etc.)に基づいた文字列解析・整形を自動で行います。
int printf(const char *format, ...);int fprintf(FILE *stream, const char *format, ...);int sprintf(char *str, const char *format, ...);— 危険(オーバーフロー)int snprintf(char *str, size_t size, const char *format, ...);— 安全版
scanf 系も同様に使用します。
int scanf(const char *format, ...);int fscanf(FILE *stream, const char *format, ...);int sscanf(const char *str, const char *format, ...);
学生データファイルの読み込み例
ファイル形式(スペース区切り)
Mike M 18 167.2 Lucy F 17 155.3
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char name[32];
char gender;
int age;
float height;
} Student;
Student *read_students(const char *filename, size_t *count) {
FILE *fp = fopen(filename, "r");
if (!fp) return NULL;
Student *list = NULL;
*count = 0;
while (1) {
Student *tmp = realloc(list, (*count + 1) * sizeof(Student));
if (!tmp) break;
list = tmp;
int res = fscanf(fp, "%31s %c %d %f",
list[*count].name,
&list[*count].gender,
&list[*count].age,
&list[*count].height);
if (res == EOF) break;
if (res != 4) {
fprintf(stderr, "Parse error at line %zu\n", *count + 1);
free(list);
fclose(fp);
return NULL;
}
(*count)++;
}
fclose(fp);
return list;
}
void print_students(const Student *students, size_t n) {
for (size_t i = 0; i < n; i++) {
printf("%-10s %c %3d %6.1f\n",
students[i].name,
students[i].gender,
students[i].age,
students[i].height);
}
}
int main(void) {
size_t n = 0;
Student *data = read_students("students.txt", &n);
if (!data) {
perror("read_students");
return EXIT_FAILURE;
}
print_students(data, n);
free(data);
return EXIT_SUCCESS;
}