第10章: 入出力
10.1 入出力の理論
ストリーム抽象化
C言語の入出力はストリーム(Stream)という抽象化に基づいている。
ストリームの概念:
プログラム ←→ ストリーム ←→ デバイス
ストリームは「バイトの流れ」を抽象化:
- 入力ストリーム: データがプログラムに流れ込む
- 出力ストリーム: データがプログラムから流れ出す
Dennis Ritchieの設計思想: > "Everything is a file"(全てはファイルである)
UNIXの哲学に基づき、キーボード、画面、ファイル、ネットワークなど、あらゆる入出力を統一的なインターフェースで扱える。
バッファリング
バッファリングの種類:
1. 完全バッファリング(Fully Buffered):
バッファが満杯になるまで実際のI/Oを遅延
[プログラム] → [バッファ: ████████] → [ファイル]
2. 行バッファリング(Line Buffered):
改行文字が来るまで遅延
[プログラム] → [バッファ: Hello\n] → [端末]
3. バッファなし(Unbuffered):
即座に出力
[プログラム] → [エラー出力]
標準ストリームのデフォルト:
stdin: 端末なら行バッファ、それ以外は完全バッファstdout: 端末なら行バッファ、それ以外は完全バッファstderr: バッファなし
10.2 標準入出力
標準ストリーム
#include <stdio.h>
/* 標準入出力ストリーム */
FILE *stdin; /* 標準入力(通常はキーボード) */
FILE *stdout; /* 標準出力(通常は画面) */
FILE *stderr; /* 標準エラー出力(通常は画面) */
/* EOF: End Of File */
#define EOF (-1)
文字入出力
/* 1文字入力 */
int getchar(void); /* stdin から1文字読む */
int fgetc(FILE *stream); /* 指定ストリームから1文字読む */
int getc(FILE *stream); /* fgetcと同等(マクロの場合あり) */
/* 1文字出力 */
int putchar(int c); /* stdout に1文字書く */
int fputc(int c, FILE *stream); /* 指定ストリームに1文字書く */
int putc(int c, FILE *stream); /* fputcと同等 */
/* 使用例 */
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
行入出力
/* 行入力 */
char *fgets(char *s, int n, FILE *stream);
/* 使用例(安全) */
char buffer[100];
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
/* 改行文字が含まれる可能性がある */
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0'; /* 改行を削除 */
}
/* gets() は危険!使用禁止(C11で削除) */
/* char *gets(char *s); ← バッファオーバーフローの危険 */
/* 行出力 */
int fputs(const char *s, FILE *stream);
int puts(const char *s); /* 自動的に改行を追加 */
fputs("Hello", stdout); /* "Hello" */
puts("Hello"); /* "Hello\n" */
フォーマット入出力
/* printf ファミリー */
int printf(const char *format, ...); /* stdout へ */
int fprintf(FILE *stream, const char *format, ...); /* ストリームへ */
int sprintf(char *str, const char *format, ...); /* 文字列へ(危険) */
int snprintf(char *str, size_t n, const char *format, ...); /* 安全 */
/* scanf ファミリー */
int scanf(const char *format, ...); /* stdin から */
int fscanf(FILE *stream, const char *format, ...); /* ストリームから */
int sscanf(const char *str, const char *format, ...);/* 文字列から */
フォーマット指定子
/* 整数 */
%d, %i /* int(10進数) */
%u /* unsigned int(10進数) */
%o /* unsigned int(8進数) */
%x, %X /* unsigned int(16進数) */
%ld /* long */
%lld /* long long */
%zu /* size_t */
/* 浮動小数点 */
%f /* double(小数点形式) */
%e, %E /* double(指数形式) */
%g, %G /* double(自動選択) */
%Lf /* long double */
/* 文字と文字列 */
%c /* char */
%s /* char * */
/* ポインタ */
%p /* void * */
/* その他 */
%% /* %自体 */
%n /* 出力文字数を取得(危険!) */
フィールド幅と精度
/* フィールド幅 */
printf("%10d", 42); /* " 42" 右寄せ */
printf("%-10d", 42); /* "42 " 左寄せ */
printf("%010d", 42); /* "0000000042" ゼロ埋め */
/* 精度 */
printf("%.5d", 42); /* "00042" 最小桁数 */
printf("%.5f", 3.14); /* "3.14000" 小数点以下桁数 */
printf("%.5s", "Hello, World"); /* "Hello" 最大文字数 */
/* フィールド幅と精度の組み合わせ */
printf("%10.5f", 3.14); /* " 3.14000" */
/* 動的なフィールド幅 */
printf("%*d", 10, 42); /* %10d と同等 */
printf("%.*f", 5, 3.14);/* %.5f と同等 */
長さ修飾子
/* 長さ修飾子 */
hh /* char */
h /* short */
l /* long, wchar_t */
ll /* long long */
j /* intmax_t */
z /* size_t */
t /* ptrdiff_t */
L /* long double */
/* 例 */
printf("%hhd", (char)127); /* char */
printf("%hd", (short)32767); /* short */
printf("%ld", 2147483647L); /* long */
printf("%lld", 9223372036854775807LL); /* long long */
printf("%zu", sizeof(int)); /* size_t */
10.3 ファイル操作
ファイルのオープンとクローズ
/* ファイルを開く */
FILE *fopen(const char *filename, const char *mode);
/* モード文字列 */
"r" /* 読み込み(ファイルが存在する必要あり) */
"w" /* 書き込み(存在すれば切り詰め、なければ作成) */
"a" /* 追記(なければ作成) */
"r+" /* 読み書き(存在する必要あり) */
"w+" /* 読み書き(切り詰めまたは作成) */
"a+" /* 読み書き(追記、なければ作成) */
/* バイナリモード */
"rb", "wb", "ab", "r+b", "w+b", "a+b"
/* 使用例 */
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("fopen");
return -1;
}
/* 処理 */
fclose(fp);
ファイルの読み書き
/* バイナリ読み書き */
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);
/* 使用例: 構造体の読み書き */
struct Record {
int id;
char name[50];
double value;
};
/* 書き込み */
struct Record rec = {1, "Test", 3.14};
FILE *fp = fopen("data.bin", "wb");
fwrite(&rec, sizeof(struct Record), 1, fp);
fclose(fp);
/* 読み込み */
struct Record rec2;
fp = fopen("data.bin", "rb");
fread(&rec2, sizeof(struct Record), 1, fp);
fclose(fp);
ファイル位置
/* 位置の取得と設定 */
long ftell(FILE *stream); /* 現在位置を取得 */
int fseek(FILE *stream, long offset, int whence); /* 位置を設定 */
void rewind(FILE *stream); /* 先頭に戻る */
/* whence の値 */
SEEK_SET /* ファイルの先頭から */
SEEK_CUR /* 現在位置から */
SEEK_END /* ファイルの末尾から */
/* 使用例 */
fseek(fp, 0, SEEK_END); /* 末尾に移動 */
long size = ftell(fp); /* ファイルサイズを取得 */
fseek(fp, 0, SEEK_SET); /* 先頭に戻る */
/* 大きなファイル用(C99) */
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
エラーとEOF
/* エラーチェック */
int ferror(FILE *stream); /* エラーフラグをチェック */
int feof(FILE *stream); /* EOFフラグをチェック */
void clearerr(FILE *stream); /* フラグをクリア */
/* 使用例 */
FILE *fp = fopen("data.txt", "r");
int c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
if (ferror(fp)) {
fprintf(stderr, "Read error occurred\n");
}
fclose(fp);
バッファ制御
/* バッファモードの設定 */
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
void setbuf(FILE *stream, char *buf);
/* mode の値 */
_IOFBF /* 完全バッファリング */
_IOLBF /* 行バッファリング */
_IONBF /* バッファなし */
/* 使用例 */
FILE *fp = fopen("data.txt", "w");
setvbuf(fp, NULL, _IONBF, 0); /* バッファなし */
/* バッファをフラッシュ */
int fflush(FILE *stream);
/* 全ての出力ストリームをフラッシュ */
fflush(NULL);
10.4 低レベル入出力(POSIX)
ファイルディスクリプタ
#include <unistd.h>
#include <fcntl.h>
/* 標準ファイルディスクリプタ */
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
/* ファイルを開く */
int open(const char *pathname, int flags, ...);
/* flags */
O_RDONLY /* 読み込み専用 */
O_WRONLY /* 書き込み専用 */
O_RDWR /* 読み書き */
O_CREAT /* 存在しなければ作成 */
O_TRUNC /* 既存の内容を切り詰め */
O_APPEND /* 追記 */
O_EXCL /* O_CREATと併用、存在すればエラー */
/* 使用例 */
int fd = open("data.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return -1;
}
close(fd);
read と write
/* 読み込み */
ssize_t read(int fd, void *buf, size_t count);
/* 書き込み */
ssize_t write(int fd, const void *buf, size_t count);
/* 使用例 */
char buffer[1024];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {
perror("read");
} else if (bytes_read == 0) {
/* EOF */
}
ssize_t bytes_written = write(fd, buffer, bytes_read);
if (bytes_written == -1) {
perror("write");
}
stdio と POSIX の対応
/* FILE * からファイルディスクリプタを取得 */
int fileno(FILE *stream);
/* ファイルディスクリプタから FILE * を作成 */
FILE *fdopen(int fd, const char *mode);
/* 使用例 */
int fd = fileno(stdout); /* 通常は 1 */
FILE *fp = fdopen(fd, "w");
fprintf(fp, "Hello via fdopen\n");
ファイル位置(POSIX)
/* 位置の設定 */
off_t lseek(int fd, off_t offset, int whence);
/* 使用例: ファイルサイズの取得 */
off_t size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET); /* 先頭に戻る */
/* 大きなファイル用 */
/* _FILE_OFFSET_BITS=64 でコンパイル */
10.5 エラー処理
errno
#include <errno.h>
#include <string.h>
/* errno: グローバルエラー番号 */
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
/* errno には最後のエラーコードが設定される */
printf("Error code: %d\n", errno);
printf("Error message: %s\n", strerror(errno));
}
perror
/* エラーメッセージを stderr に出力 */
void perror(const char *s);
/* 使用例 */
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("fopen");
/* 出力: fopen: No such file or directory */
}
一般的なエラーコード
EACCES /* Permission denied */
EEXIST /* File exists */
EINTR /* Interrupted system call */
EINVAL /* Invalid argument */
EIO /* I/O error */
EISDIR /* Is a directory */
EMFILE /* Too many open files */
ENOENT /* No such file or directory */
ENOMEM /* Out of memory */
ENOSPC /* No space left on device */
ENOTDIR /* Not a directory */
EPERM /* Operation not permitted */
エラー処理パターン
/* パターン1: 即座にリターン */
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return -1;
}
/* パターン2: goto によるクリーンアップ */
int process_file(const char *filename)
{
int ret = -1;
FILE *fp = NULL;
char *buffer = NULL;
fp = fopen(filename, "r");
if (fp == NULL) {
perror("fopen");
goto cleanup;
}
buffer = malloc(BUFFER_SIZE);
if (buffer == NULL) {
perror("malloc");
goto cleanup;
}
/* 処理 */
ret = 0;
cleanup:
free(buffer);
if (fp)
fclose(fp);
return ret;
}
/* パターン3: EINTR の再試行 */
ssize_t safe_read(int fd, void *buf, size_t count)
{
ssize_t ret;
do {
ret = read(fd, buf, count);
} while (ret == -1 && errno == EINTR);
return ret;
}
10.6 一時ファイル
#include <stdio.h>
/* 一時ファイルを作成 */
FILE *tmpfile(void);
/* 自動的に削除される、バイナリ読み書きモード */
/* 一時ファイル名を生成 */
char *tmpnam(char *s); /* 非推奨: 競合状態の危険 */
/* より安全な方法(POSIX) */
#include <stdlib.h>
int mkstemp(char *template);
/* 使用例 */
char template[] = "/tmp/myapp.XXXXXX";
int fd = mkstemp(template);
if (fd == -1) {
perror("mkstemp");
return -1;
}
/* template は実際のファイル名に変更される */
printf("Created: %s\n", template);
unlink(template); /* ファイルを削除(fdは有効なまま) */
/* ... fd を使用 ... */
close(fd);
10.7 入出力のパフォーマンス
バッファサイズの選択
/* 最適なバッファサイズ */
#include <sys/stat.h>
struct stat st;
if (fstat(fd, &st) == 0) {
size_t optimal_size = st.st_blksize; /* ファイルシステムのブロックサイズ */
}
/* 一般的な選択肢 */
#define BUFSIZ 8192 /* stdio のデフォルト */
/* または 4096, 8192, 16384 など */
効率的なファイルコピー
/* 非効率: 1バイトずつ */
int c;
while ((c = fgetc(src)) != EOF)
fputc(c, dst);
/* 効率的: バッファを使用 */
char buffer[8192];
size_t bytes;
while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0)
fwrite(buffer, 1, bytes, dst);
/* さらに効率的: mmap(大きなファイル) */
#include <sys/mman.h>
void *mapped = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
write(dst_fd, mapped, file_size);
munmap(mapped, file_size);
メモリマップドI/O
#include <sys/mman.h>
/* ファイルをメモリにマップ */
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
/* マップを解除 */
int munmap(void *addr, size_t length);
/* prot: 保護 */
PROT_READ /* 読み取り可能 */
PROT_WRITE /* 書き込み可能 */
PROT_EXEC /* 実行可能 */
PROT_NONE /* アクセス不可 */
/* flags */
MAP_SHARED /* 変更を他のプロセスと共有 */
MAP_PRIVATE /* コピーオンライト */
/* 使用例 */
int fd = open("data.bin", O_RDONLY);
struct stat st;
fstat(fd, &st);
void *data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
/* data をポインタとして直接アクセス */
char *text = (char *)data;
printf("First char: %c\n", text[0]);
munmap(data, st.st_size);
close(fd);
10.8 まとめ
本章では、C言語の入出力について学んだ:
次章では、コンパイルとリンクについて学ぶ。
---