ユーニックス総合研究所

  • home
  • archives
  • c-file-yomikomi

C言語のファイルの読み込み方法【fopen, fclose, fgets, fgetc, fread, read】

  • 作成日: 2024-02-25
  • 更新日: 2024-03-15
  • カテゴリ: C言語

C言語ではファイルの内容を読み込みたい場合はfopen系の関数などが使えます。
これらの関数はそれぞれ役割が違っていて目的によって使い分ける必要があります。
この記事ではこれらの関数の基本的な使い方を解説します。

C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。

Youtubeの当チャンネル

関連動画
C言語でテキストファイルの内容を読み込む【read_file】 - YouTube

読み込む対象のファイルの内容

今回のサンプルプログラムで使う読み込み対象のファイルの内容は以下になります。

Hello!  
World!  
Good bye!  

これをfile.txtというファイル名で保存しています。

基本

fopen系の基本となる関数は以下の5つになります。

  • fopen ... ファイルを開く
  • fclose ... ファイルを閉じる
  • fgets ... 1行読み込む
  • fgetc ... 1文字読み込む
  • fread ... 指定サイズを読み込む

これらの関数はstdio.hをインクルードする必要があります。

#include <stdio.h>  

またexit()などの関数はstdlib.hをインクルードする必要があります。

#include <stdlib.h>  

fopen関数でファイルを開く

fopen関数はファイルを開く関数です。
開いたファイルはFILE構造体でラップされており高度に抽象化されています。

関連記事
C言語のfopen関数でファイルを開く方法【fcloseも解説】

#include <stdio.h>  

FILE *fopen(  
    const char *ファイルのパス,  
    const char *モード  
);  

成功時の返り値:ストリーム  
失敗時の返り値:NULL(errnoをセット)  
    // ファイルを読み込みモードで開く  
    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

fopen()は第1引数にファイルのパスを取ります。ここに開きたいファイルのパスを指定します。
第2引数にはファイルのオープンモードを指定します。モードは複数あり用途に合わせて指定します。詳しくは後述します。

fopen()はファイルを開いた場合はFILE構造体へのポインタを返します。
これは動的にメモリ確保されていますので、fclose()で閉じて解放する必要があります。
ファイルを開くのに失敗した場合はNULLを返しerrnoをセットします。

fopen()でエラーハンドリングする場合はfopen()の返り値をチェックし、NULLであればエラー処理をするようにしてください。
上記の例ではperror()errnoを参照してエラーメッセージを出力し、その後にexit()で終了しています。
exit()EXIT_FAILURE定数を渡すと失敗時の終了ステータスになります。

読み込みモードの種類

fopen()の読み込み系のモードの種類は以下になります。

  • r ... テキストファイルの読み込みモード
  • rt ... テキストファイルの読み込みモード
  • rb ... バイナリファイルの読み込みモード
  • r+ ... テキストファイルの読み込みと書き込みモード
  • rb+ ... バイナリファイルの読み込みと書き込みモード

テキストファイルを読み込みたい場合はr, rt, r+のいずれかを使います。
バイナリファイルを読み込みたい場合はrb, rb+のいずれかになります。
rrtと同じですのでどちらを使ってもかまいません。

fclose関数でファイルを閉じる

ファイルと言うのは開いたら閉じないといけません。
開いたファイルを閉じないとファイルが開きっぱなしになったりするなど不具合が出ることがあります。
fopen()で開いたファイルはfclose()で閉じます。

#include <stdio.h>  

int fclose(FILE *ストリーム)  

成功時の返り値:0  
失敗時の返り値:EOF(errnoをセット)  
    // ファイルを読み込みモードで開く  
    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // ファイルを閉じる  
    fclose(fin);  

fclose()はファイルを閉じるのに失敗するとEOFを返してerrnoをセットします。
ですがfclose()のエラーハンドリングをしている人はあまり見かけません。
閉じるのがかなりクリティカルなことであればエラーハンドリングした方がいいでしょう。

fgets関数で1行読み込む

fgets()はファイルから1行読み込む関数です。

関連記事
C言語のfgetsを使う方法

#include <stdio.h>  

char *fgets(  
    char *バッファ,  
    int バッファのサイズ,  
    FILE *ストリーム  
);  

成功時の返り値:バッファ  
失敗時の返り値:NULL(errnoをセット)  
ファイル終端に達したときの返り値:NULL  
    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // 読み込むデータを入れておくバッファ  
    char buf[128];  

    // fgets()で一行読み込み  
    fgets(buf, sizeof buf, fin);  

    // 読み込んだ内容を出力  
    printf("buf[%s]\n", buf);  
    /*  
        buf[Hello!  
        ]  
    */  

    fclose(fin);  

fgets()の第1引数にはデータを格納するバッファを指定します。
バッファはchar *型です。
第2引数にはバッファのサイズを指定します。
第3引数にはストリームを指定します。

fgets()の読み込む一行というのは改行までの一行です。
この時読み込まれるデータは改行が含まれます。
読み込んだバッファはナル文字で終端されます。

この関数はファイルを1行ずつ読み込みたい場合に使います。
エラー時とファイル終端に達した時にNULLを返しますのでこれでファイル終端を検出します。

エラーとファイル終端の区別をしたい場合はferror()feof()を使います。

fgetc関数で1文字読み込み

ストリームから1文字読み込む関数がfgetc()です。

#include <stdio.h>  

int fgetc(FILE *ストリーム);  

成功時の返り値:読み込んだ文字  
失敗時の返り値:EOF(errnoをセット)  
ファイル終端時の返り値:EOF  
    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // fgetc()で一文字読み込み  
    int c = fgetc(fin);  
    if (ferror(fin) != 0) {  
        perror("fgetc");  
        exit(EXIT_FAILURE);  
    }  

    // 読み込んだ内容を出力  
    printf("c[%c]\n", c);  
    /*  
        c[H]  
    */  

    fclose(fin);  

fgetc()はストリームから1文字読み込みます。
返り値はint型です。
EOFを返す場合はエラーかファイル終端です。
エラーかファイル終端かチェックしたい場合はferror()feof()を使います。

fread関数でまとめて読み込み

fread()はファイル内容をある程度まとめて読み込みたい場合に使います。

#include <stdio.h>  

size_t fread(  
    void *バッファ,  
    size_t バッファの1要素のサイズ,  
    size_t バッファの要素数,  
    FILE *ストリーム  
);  

成功時の返り値:読み込んだ要素数  
失敗時の返り値:指定した要素数より少ない要素数(errnoをセット)  
ファイル終端時の返り値:指定した要素数より少ない要素数  
    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // 読み込むデータを入れておくバッファ  
    char buf[128];  

    // fread()でファイル内容を読み込み  
    int nread = fread(buf, sizeof(buf[0]), 127, fin);  
    if (ferror(fin) != 0) {  
        perror("fread");  
        fclose(fin);  
        exit(EXIT_FAILURE);  
    }  

    buf[nread] = '\0';  // ナル文字で終端  

    /*  
        buf[Hello!  
        World!  
        Good bye!  
        ]  
    */  

    // 読み込んだ内容を出力  
    printf("buf[%s]\n", buf);  

    fclose(fin);  

fread()の第1引数にはバッファへのポインタを指定します。void *型ですのでchar *も渡せます。
第2引数にはバッファの要素1つのサイズを指定します。
第3引数にはバッファの要素数を指定します。
第4引数にはストリームを指定します。

fread()は読み込んだ要素数を返しますので、その値を使ってバッファをナル終端します。
上記の例では128要素のバッファを指定しています。
読み込む要素数は127です。これはナル文字分空けておくためです。

高度

高度なファイル読み込みの処理に使われる関数は以下が代表的です。

  • open ... ファイルディスクリプタを開く
  • close ... ファイルディスクリプタを閉じる
  • raed ... ファイル内容を読み込む

これらのI/Oはファイルディスクリプタを使うのが特徴です。
ファイルディスクリプタはFILE構造体よりも一段階低レベルなレイヤーのI/Oになります。
fopen()などは内部でopen()などを使っていると思われますが、それらを抽象化して使いやすくしたものです。
低レイヤーなのでfopen()系よりも色々な細かい指定ができます。

ファイルディスクリプタというのはファイルとの接続を表す整数のことです。
ただのintです。

これらの関数の使用は以下のヘッダーをインクルードする必要があります。

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <unistd.h>  

open関数でファイルディスクリプタを開く

open()はファイルディスクリプタを開くための関数です。

#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  

int open(const char *ファイルのパス, int フラグ(複数可));  

成功時の返り値:ファイルディスクリプタ  
失敗時の返り値:-1(errnoをセット)  
    // ファイルディスクリプタを開く  
    int fd = open("file.txt", O_RDONLY);  
    if (fd == -1) {  
        perror("open");  
        exit(EXIT_FAILURE);  
    }  

open()のフラグを指定するときはO_RDONLY | O_CREATのように|で繋げて指定します。
O_RDONLYは読み込み専用でファイルを開くフラグです。
O_CREATはファイルが存在しない場合にファイルを作成するフラグです。
open()のフラグは沢山ありますので調べてみてください。

close関数でファイルディスクリプタを閉じる

ファイルディスクリプタを閉じる関数がclose()です。

#include <unistd.h>  

int close(int ファイルディスクリプタ);  

成功時の返り値:0  
失敗時の返り値:-1(errnoをセット)  
    // ファイルディスクリプタを閉じる  
    close(fd);  

ファイルディスクリプタもストリームと同様に開いたら閉じないいけません。

read関数でファイルを読み込む

ファイルディスクリプタからデータを読み込むのがread()です。

#include <unistd.h>  

ssize_t read(  
    int ファイルディスクリプタ,  
    void *バッファ,  
    size_t 読み込むバイト数  
);  

成功時の返り値:読み込んだバイト数  
失敗時の返り値:-1(errnoをセット)  
    int fd = open("file.txt", O_RDONLY);  
    if (fd == -1) {  
        perror("open");  
        exit(EXIT_FAILURE);  
    }  

    // データを入れておくバッファ  
    char buf[128];  

    // ファイル内容の読み込み  
    int nread = read(fd, buf, 127);  
    if (nread == -1) {  
        perror("read");  
        close(fd);  
        exit(EXIT_FAILURE);  
    }  

    buf[nread] = '\0';  // ナル文字をお尻に入れておく  

    // 読み込んだ内容を出力  
    printf("buf[%s]\n", buf);  
    /*  
        buf[Hello!  
        World!  
        Good bye!  
        ]  
    */  

    close(fd);  

read()は第1引数にファイルディスクリプタを指定します。
第2引数にバッファへのポインタ、第3引数に読み込むバイト数を指定します。

read()はソケット通信などにも使われるわりとポピュラーな関数です。
そのためこの関数の仕様を覚えておくのは意味のあることでしょう。

読み込みに成功すると読み込んだバイト数を返しますので、そのバイト数を使ってバッファをナル終端できます。
ただしこれはバッファがcharなどの1バイト型である場合に限ります。

読み込みのテクニック

ここからは読み込み処理のよく見かける定型文を紹介します。
これらの定型文を押さえておくといろいろ改造して発展させることができるかもしれません。

fgetsを使った定型文

fgets()でファイルの行をすべて取得して表示する定型文です。

    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // 行データを入れておくバッファ  
    char line[128];  

    // ループ文で行データを読み込み  
    while (fgets(line, sizeof line, fin)) {  
        printf("line[%s]\n", line);  
    }  

    fclose(fin);  

fgets()はファイル終端に達するとNULLを返します。
ですのでwhile文で条件式に使って、ファイル終端までfgets()を実行するようにします。
読み込んだlineprintf()で出力します。
lineには改行も含まれます。

fgetcを使った定型文

fgetc()でファイル内容をすべて取得して表示する定型文です。

    FILE *fin = fopen("file.txt", "r");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // fgetc()で1文字ずつ読み込んでいく  
    for (int c; (c = fgetc(fin)) != EOF; ) {  
        putchar(c);  // 出力  
    }   

    fclose(fin);  

fgetc()はファイル終端に達するとEOFを返します。
これをループ文の条件式に使ってファイル終端まで1文字ずつ取得するようにします。
取得した文字cputchar()で標準出力に出力します。

freadを使った定型文

fread()でファイル内容をすべて取得する定型文です。

    FILE *fin = fopen("file.txt", "r+");  
    if (fin == NULL) {  
        perror("fopen");  
        exit(EXIT_FAILURE);  
    }  

    // ファイル終端にシーク  
    fseek(fin, 0, SEEK_END);  

    // 終端でのファイルサイズを取得  
    long size = ftell(fin);  

    // ファイル先頭にシーク  
    fseek(fin, 0, SEEK_SET);  

    // 動的メモリを確保  
    char *buf = malloc(size + 1);  // +1はナル文字分  
    if (buf == NULL) {  
        perror("malloc");  
        exit(EXIT_FAILURE);  
    }  

    // 読み込み  
    fread(buf, sizeof(buf[0]), size, fin);  
    buf[size] = '\0';  // ナル文字で終端する  

    // 読み込んだ内容を出力  
    printf("buf[%s]\n", buf);  

    // 動的メモリを解放  
    free(buf);  

    fclose(fin);       

fopen()のオープンモードをr+にします。こうするとストリームをシークできるようになります。

    FILE *fin = fopen("file.txt", "r+");  

シークとはストリームの読み込み位置をプログラム的に変更することを言います。
このシークができるようになるとファイル終端に移動したりファイル先頭に移動したりが自由にできるようになります。

    // ファイル終端にシーク  
    fseek(fin, 0, SEEK_END);  

    // 終端でのファイルサイズを取得  
    long size = ftell(fin);  

    // ファイル先頭にシーク  
    fseek(fin, 0, SEEK_SET);  

上記のようにfseek()でファイル終端に移動してからftell()でストリームの現在位置のバイト数を得ます。
こうするとファイルサイズを得ることができます。
ファイル終端にシークした後は再びファイル先頭にシークするのを忘れないようにします。
ちなみにこのような操作をランダムアクセスと言います。

    // 動的メモリを確保  
    char *buf = malloc(size + 1);  // +1はナル文字分  
    if (buf == NULL) {  
        perror("malloc");  
        exit(EXIT_FAILURE);  
    }  

上記のようにファイルサイズsizeを得られたら動的メモリをアロケートします。
malloc()は動的メモリを確保するための関数です。
動的メモリとはヒープ領域からメモリを取ってくることを言います。
静的でない動的なメモリ領域です。

malloc()の第1引数には確保したいバイト数を指定します。
size + 1にしてナル文字分余計に確保するのを忘れないようにします。
malloc()はメモリ確保に失敗するとNULLを返します。
ですのでこれをエラーハンドリングします。

    // 読み込み  
    fread(buf, sizeof(buf[0]), size, fin);  
    buf[size] = '\0';  // ナル文字で終端する  

    // 読み込んだ内容を出力  
    printf("buf[%s]\n", buf);  

上記のように動的に確保したbufにストリームから読み込んだデータを格納します。
buf[size] = '\0';でバッファのお尻にナル文字を入れておくのを忘れないようにします。

printf()でバッファの内容を出力します。

    // 動的メモリを解放  
    free(buf);  

    fclose(fin);       

上記のように動的メモリはfree()で解放する必要があります。
解放を忘れるとメモリリークというバグになります。

ファイルの読み込みの注意事項

fopenのモードを「w」にしないように!

fopen()ですがモードを「w」にしないように注意してください。
wは破壊的にファイルを開きますので、大事なデータが空になっちゃった! ということもよくあります。
(よくあっちゃまずいんですが)

fopen()のモードを指定するときはwについては少し神経質になっておいてもいいかもしれません。

🦝 < wだけどわろえない

🐭 < 破壊的オープン!