Linux C 言語における標準 I/O ライブラリの詳細と実装

標準 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 マークは -1char に収まらない可能性があるため)。

  • 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;
}

タグ: c-language standard-I/O fopen fread fprintf

6月9日 19:11 投稿