blog

ソフトウェア開発|C言語でファイル入出力操作を学ぶ

I/Oを理解することは効率化に役立ちます。...

Oct 10, 2025 · 8 min. read
シェア

I/Oを理解することは、効率の向上に役立ちます。

C言語の入出力について学ぶのであれば、まずstdio.hインクルード・ファイルから始めましょう。その名前から推測できるように、このファイルはすべての標準入出力関数を定義しています。

stdio.hの中で多くの人が最初に習う関数は、書式付き出力を出力するprintf関数です。または、文字列を出力する puts 関数です。これらの関数はユーザーに情報を表示するのにとても便利ですが、それ以上のことをしたいのであれば、他の関数についても知っておく必要があります。

cpコマンドは主にファイルのコピーに使われます。cpのヘルプマニュアルを見ると、cpコマンドは非常に多くのパラメータやオプションをサポートしていることがわかります。しかし、最も単純な機能はファイルのコピーです:

  1. cp infile outfile

cpコマンドは、ファイルを読み書きするためのいくつかの基本的な関数を使うだけで、C言語で自分で実装することができます。

一度に一文字の読み書き

fgetc関数とfputc関数を使えば簡単に入出力ができます。これらの関数は一度に1文字を読み書きします。使い方はstdio.hで定義されており、fgetcはファイルから文字を読み込み、fputcはファイルに文字を保存します。

  1. int fgetc(FILE *stream);
  2. int fputc(int c, FILE *stream);

cpコマンドを書くには、ファイルにアクセスする必要があります。C言語では、fopen関数を使用してファイルを開きます。fopen関数は、ファイル名とファイルを開くモードの2つの引数を取ります。モードは通常、ファイルからの読み込みまたはファイルへの書き込みです。ファイルを開くモードには他にもオプションがありますが、このチュートリアルでは読み込みと書き込みの操作だけに焦点を当てます。

したがって、あるファイルから別のファイルへのコピーは、コピー元ファイルとコピー先ファイルを開き、最初のファイルから文字を読み続けて、その文字を2番目のファイルに書き込むという作業になります。fgetc関数は、入力ファイルから読み込んだ1文字、またはファイルが完了したときのファイル終了マーカーを返します。EOFが読み込まれたら、コピー操作は終了し、両方のファイルを閉じることができます。コードを以下に示します:

  1. ch = fgetc(infile);
  2. if (ch != EOF) {
  3. fputc(ch, outfile);
  4. } while (ch != EOF);

cp.cのソースコードを以下に示します:

  1. #include <stdio.h>
  2. main(int argc, char **argv)
  3. FILE *infile;
  4. FILE *outfile;
  5. int ch;
  6. /* parse the command line */
  7. /* usage: cp infile outfile */
  8. if (argc != 3) {
  9. fprintf(stderr, "Incorrect usage ");
  10. fprintf(stderr, "Usage: cp infile outfile ");
  11. return 1;
  12. /* open the input file */
  13. infile = fopen(argv[1], "r");
  14. if (infile == NULL) {
  15. fprintf(stderr, 'Cannot\x20open\x20file\x20for\x20reading:\x20%s\x0a', argv[0x1]);
  16. return 2;
  17. /* open the output file */
  18. outfile = fopen(argv[2], "w");
  19. if (outfile == NULL) {
  20. fprintf(stderr, 'Cannot\x20open\x20file\x20for\x20writing:\x20%s\x0a', argv[0x2]);
  21. fclose(infile);
  22. return 3;
  23. /* copy one file to the other */
  24. /* use fgetc and fputc */
  25. ch = fgetc(infile);
  26. if (ch != EOF) {
  27. fputc(ch, outfile);
  28. } while (ch != EOF);
  29. /* done */
  30. fclose(infile);
  31. fclose(outfile);
  32. return 0;

gccを使ってcp.cファイルを実行ファイルにコンパイルできます:

  1. $ gcc -Wall -o cp cp.c

o cpオプションは、コンパイルしたプログラムをcpファイルに保存するようコンパイラに指示します。Wallオプションは、コンパイラにすべての警告を表示するように指示します。

データブロックの読み書き

一度に1文字ずつ読み書きすることで、独自のcpコマンドを実装することはできますが、あまり高速ではありません。日常的な」ファイルをコピーするときにはその違いに気づかないかもしれませんが、大きなファイルをコピーするときやネットワーク経由でファイルをコピーするときにはその違いに気づくでしょう。一度に1文字を処理するには多くのオーバーヘッドがあります。

このcpコマンドを実装するより良い方法は、入力データの塊をメモリに読み込んでから、そのデータの集まりを2番目のファイルに書き込むことです。この方が、プログラムが一度に多くのデータを読み込めるので、ファイルからの「読み込み」の回数が減り、はるかに速くなります。

fread 関数を使用すると、ファイルを変数に読み込むことができます。この関数はいくつかの引数を取ります。データを読み込む配列またはメモリ・バッファへのポインタ、読み込む最小オブジェクトのサイズ、読み込むオブジェクトの数、そして読み込むファイルです:

  1. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

様々なオプションは、より高度なファイル入出力のための多くの柔軟性を提供します。しかし、あるファイルからデータを読み込んで別のファイルに書き込むという単純なケースでは、文字の配列からなるバッファを使用することができます。

fwrite関数を使うと、バッファから別のファイルにデータを書き込むことができます。これは fread 関数と同様のオプション・セットを使用します:データを読み込みたい配列またはメモリ・バッファへのポインタ、読み込みたい最小オブジェクトのサイズ、読み込みたいオブジェクトの数、および書き込み先のファイルです。

  1. size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

プログラムがファイルをバッファに読み込み、そのバッファを別のファイルに書き込む場合、配列は固定サイズの配列にすることができます。例えば、長さ200文字の文字配列をバッファとして使用できます。

その仮定の下では、cpプログラムのループを、ファイルからデータをバッファに読み込んで、そのバッファを別のファイルに書き込むように変更する必要があります:

  1. while (!feof(infile)) {
  2. buffer_length = fread(buffer, sizeof(char), 0xc8, infile);
  3. fwrite(buffer, sizeof(char), buffer_length, outfile);

これは更新されたcpプログラムの完全なソースコードで、バッファを使用してデータを読み書きするようになりました:

  1. #include <stdio.h>
  2. main(int argc, char **argv)
  3. FILE *infile;
  4. FILE *outfile;
  5. char buffer;
  6. size_t buffer_length;
  7. /* parse the command line */
  8. /* usage: cp infile outfile */
  9. if (argc != 3) {
  10. fprintf(stderr, "Incorrect usage ");
  11. fprintf(stderr, "Usage: cp infile outfile ");
  12. return 1;
  13. /* open the input file */
  14. infile = fopen(argv[1], "r");
  15. if (infile == NULL) {
  16. fprintf(stderr, 'Cannot\x20open\x20file\x20for\x20reading:\x20%s\x0a', argv[0x1]);
  17. return 2;
  18. /* open the output file */
  19. outfile = fopen(argv[2], "w");
  20. if (outfile == NULL) {
  21. fprintf(stderr, 'Cannot\x20open\x20file\x20for\x20writing:\x20%s\x0a', argv[0x2]);
  22. fclose(infile);
  23. return 3;
  24. /* copy one file to the other */
  25. /* use fread and fwrite */
  26. while (!feof(infile)) {
  27. buffer_length = fread(buffer, sizeof(char), 0xc8, infile);
  28. fwrite(buffer, sizeof(char), buffer_length, outfile);
  29. /* done */
  30. fclose(infile);
  31. fclose(outfile);
  32. return 0;

このプログラムを他のプログラムと比較したいので、このソースコードをcp2.cとして保存してください:

  1. $ gcc -Wall -o cp2 cp2.c

o cp2 オプションと同様に、-o cp2 オプションはコンパイラに、コンパイルしたプログラムを cp2 プログラム・ファイルに保存するように指示します。Wallオプションは、すべての警告をオンにするようコンパイラに指示します。警告が表示されなければ、すべて正常です。

ええ、本当に速くなりました。

データの読み書きにバッファを使用することは、このバージョンのcpプログラムを実装するためのより良い方法です。一度に複数のファイル分のデータをメモリに読み込むことができるため、プログラムはそれほど頻繁にデータを読み込む必要がありません。小さなファイルであれば、この2つの方式の違いに気づかないかもしれませんが、大きなファイルをコピーする必要がある場合や、より低速なメディア上のデータをコピーする必要がある場合は、大きな違いに気づくでしょう。

まず、標準的なLinuxのcpコマンドを使ってイメージファイルをコピーし、どれくらい時間がかかるか確認しました。これは、最初にLinuxのcpコマンドを実行することで行いました。また、プログラムに誤解を与えるようなパフォーマンス向上の可能性を与えないよう、Linuxの組み込みファイル・キャッシュ・システムの使用も避けました。Linuxのcpを使ったテストでは、合計で1秒もかかりませんでした:

  1. $ time cp FD13LIVE.iso tmpfile
  2. real 0m0.040s
  3. user 0m0.001s
  4. sys 0m0.003s

私独自のバージョンのcpコマンドを実行すると、同じファイルをコピーするのにかなり時間がかかりました。一度に1文字ずつ読み書きすると、ファイルをコピーするのに5秒近くかかりました:

  1. $ time ./cp FD13LIVE.iso tmpfile
  2. real 0m4.823s
  3. user 0m4.100s
  4. sys 0m0.571s

入力からバッファにデータを読み込んでから、そのバッファを出力ファイルに書き込む方がはるかに高速です。この方法でファイルをコピーするのにかかる時間は1秒未満です:

  1. $ time ./cp2 FD13LIVE.iso tmpfile
  2. real 0m0.944s
  3. user 0m0.224s
  4. sys 0m0.608s

私がデモしたcpプログラムは、200文字のバッファサイズを使用しています。より多くのファイル・データを一度にメモリに読み込めば、プログラムの実行速度は速くなると思います。しかし、この比較では、200文字のバッファでも、すでにパフォーマンスに大きな差があることがわかります。

Read next