C言語ファイル入出力の教科書:初心者から上級者まで


ファイル入出力の基礎

ファイル入出力とは、プログラムがファイルにデータを書き込んだり、ファイルからデータを読み込んだりする操作を指します。これは、プログラム実行中に得られた結果を保存したり、過去のデータを読み込んで処理したりするなど、様々な場面で活用されます。

C言語では、ファイル入出力を行うために、以下の2つの主要な関数群が提供されています。

  • <stdio.h> ヘッダーファイル に含まれる関数群:ファイルに対する入出力操作を可能にします。
  • stdio.h ヘッダーファイル に含まれる関数群:標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)といった標準ストリームに対する入出力操作を可能にします。

ファイル操作の基本手順

ファイル入出力の基本手順は以下の通りです。

  1. ファイルを開く
    fopen() 関数を使用して、ファイルを開き、ファイルへのポインタを取得します。
  2. ファイル操作
    取得したファイルポインタを使用して、ファイルへの書き込み (fprintf()) や読み込み (fscanf()) などの操作を行います。
  3. ファイルを閉じる
    fclose() 関数を使用して、ファイルを閉じ、ファイルポインタを解放します。

主要なファイル入出力関数

以下、ファイル入出力に関わる主要な関数をいくつか紹介します。

  • ferror()
    ファイル操作中にエラーが発生しているかどうかを確認する関数です。
  • feof()
    ファイルの終端に到達しているかどうかを確認する関数です。
  • fseek()
    ファイルポインタの位置を移動する関数です。
  • fputc()
    ファイルに1文字ずつ書き込む関数です。
  • fgetc()
    ファイルから1文字ずつ読み込む関数です。
  • fscanf()
    ファイルからデータを読み込む関数です。引数として、ファイルポインタ、書式指定文字列、読み込むデータを格納する変数などを指定します。
  • fprintf()
    ファイルにデータを書き込む関数です。引数として、ファイルポインタ、書式指定文字列、書き込むデータなどを指定します。
  • fclose()
    ファイルを閉じ、ファイルポインタを解放する関数です。
  • fopen()
    ファイルを開き、ファイルポインタを返す関数です。引数として、ファイル名と開くモードを指定します。

例1:テキストファイルへの書き込み

以下のプログラムは、"data.txt" という名前のテキストファイルに "Hello, World!" という文字列を書き込むものです。

#include <stdio.h>

int main() {
  FILE *fp;

  fp = fopen("data.txt", "w");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  fprintf(fp, "Hello, World!\n");

  fclose(fp);

  return 0;
}

例2:テキストファイルからの読み込み

以下のプログラムは、"data.txt" という名前のテキストファイルの内容を読み込み、コンソールに出力するものです。

#include <stdio.h>

int main() {
  FILE *fp;
  char buf[256];

  fp = fopen("data.txt", "r");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  while (fgets(buf, sizeof(buf), fp) != NULL) {
    printf("%s", buf);
  }

  fclose(fp);

  return 0;
}

上記以外にも、C言語ではバイナリファイルの入出力、様々な種類のファイル形式への読み書き、ディレクトリ操作など、様々なファイル操作が可能です。より高度なファイル操作については、C言語の標準ライブラリや専門書籍などを参照してください。



ファイルへの書き込み

1 テキストファイルへの書き込み

#include <stdio.h>

int main() {
  FILE *fp;

  fp = fopen("data.txt", "w");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  fprintf(fp, "Hello, World!\n");

  fclose(fp);

  return 0;
}

2 バイナリファイルへの書き込み

以下のプログラムは、"data.bin" という名前のバイナリファイルに 4 バイトの整数を書き込むものです。

#include <stdio.h>

int main() {
  FILE *fp;
  int data = 1234;

  fp = fopen("data.bin", "wb");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  fwrite(&data, sizeof(int), 1, fp);

  fclose(fp);

  return 0;
}

1 テキストファイルからの読み込み

#include <stdio.h>

int main() {
  FILE *fp;
  char buf[256];

  fp = fopen("data.txt", "r");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  while (fgets(buf, sizeof(buf), fp) != NULL) {
    printf("%s", buf);
  }

  fclose(fp);

  return 0;
}

2 バイナリファイルからの読み込み

以下のプログラムは、"data.bin" という名前のバイナリファイルから 4 バイトの整数を読み込み、コンソールに出力するものです。

#include <stdio.h>

int main() {
  FILE *fp;
  int data;

  fp = fopen("data.bin", "rb");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");
    return 1;
  }

  fread(&data, sizeof(int), 1, fp);

  printf("読み込んだ整数: %d\n", data);

  fclose(fp);

  return 0;
}

1 ファイルのコピー

以下のプログラムは、"source.txt" という名前のファイルを "copy.txt" という名前のファイルにコピーするものです。

#include <stdio.h>

int main() {
  FILE *fp_src, *fp_dst;
  char buf[256];

  fp_src = fopen("source.txt", "r");
  if (fp_src == NULL) {
    printf("ソースファイルを開けませんでした。\n");
    return 1;
  }

  fp_dst = fopen("copy.txt", "w");
  if (fp_dst == NULL) {
    printf("コピー先ファイルを開けませんでした。\n");
    fclose(fp_src);
    return 1;
  }

  while (fgets(buf, sizeof(buf), fp_src) != NULL) {
    fputs(buf, fp_dst);
  }

  fclose(fp_src);
  fclose(fp_dst);

  return 0;
}

2 ファイルの末尾に追記

以下のプログラムは、"data.txt" という名前のファイルの末尾に "Hello, World!" という文字列を追記するものです。

#include <stdio.h>

int main() {
  FILE *fp;

  fp = fopen("data.txt", "a");
  if (fp == NULL) {
    printf("ファイルを開けませんでした。\n");


システムコール

C言語では、open(), read(), write(), close() などのシステムコールを用いて、より低レベルなファイル入出力操作を行うことができます。標準入出力関数よりも細かい制御が可能ですが、複雑さも増し、エラー処理も自分で行う必要が生じます。

利点

  • デバイスファイルやネットワークソケットなど、標準入出力関数では扱えないファイルへの入出力が可能
  • より細かい入出力制御が可能
  • 標準入出力関数よりも高速な場合がある

欠点

  • ポータビリティが損なわれる可能性がある
  • 可読性が低くなる
  • 標準入出力関数よりも複雑で、エラー処理も自分で行う必要がある


#include <unistd.h>

int main() {
  int fd;
  char buf[256];
  int bytes_read;

  fd = open("data.txt", O_RDONLY);
  if (fd == -1) {
    perror("open");
    exit(1);
  }

  bytes_read = read(fd, buf, sizeof(buf));
  if (bytes_read == -1) {
    perror("read");
    exit(1);
  }

  write(1, buf, bytes_read);

  close(fd);

  return 0;
}

ライブラリ

C言語には、ファイル入出力に関する様々なライブラリが存在します。例えば、以下のライブラリが挙げられます。

  • zlib
    gzip圧縮/解凍を行うライブラリ
  • libxml2
    XMLファイルの読み書きを可能にするライブラリ
  • libsqlite3
    SQLiteデータベースとのやり取りを可能にするライブラリ
  • libpq
    PostgreSQLデータベースとのやり取りを可能にするライブラリ

これらのライブラリは、特定のファイル形式やデータソースへの入出力を簡略化するために役立ちます。

利点

  • 豊富な機能を提供している場合がある
  • 特定のファイル形式やデータソースへの入出力を簡略化できる
  • 標準入出力関数よりも使いやすく、可読性が高い場合がある

欠点

  • ライブラリのバージョンや互換性に注意する必要がある
  • 標準入出力関数よりも非効率的な場合がある
  • 追加のライブラリをインストールする必要がある


#include <stdio.h>
#include <sqlite3.h>

int main() {
  sqlite3 *db;
  char *sql;
  int rc;

  rc = sqlite3_open("data.db", &db);
  if (rc != SQLITE_OK) {
    fprintf(stderr, "データベースを開けませんでした: %s\n", sqlite3_errmsg(db));
    return 1;
  }

  sql = "SELECT * FROM data";
  rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
  if (rc != SQLITE_OK) {
    fprintf(stderr, "SQL実行エラー: %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    return 1;
  }

  sqlite3_close(db);

  return 0;
}

メモリマッピングを用いると、ファイルをメモリ空間の一部としてマッピングすることができ、あたかも巨大な配列を操作するようにファイル入出力を行うことができます。大規模なファイルの読み書きに有効ですが、複雑さも増します。

利点

  • ファイル全体をバッファリングする必要がない
  • 大規模なファイルの読み書きを高速化できる

欠点

  • ポータビリティが損なわれる可能性がある
  • メモリ使用量が多くなる
  • 複雑で、エラー処理も自分で行う必要がある
#include <sys/mman.h>
#include <fcntl.h>

int main() {
  void *ptr;
  size_t size;
  int fd;

  fd = open("data.txt", O_RDONLY);
  if (