ファイルサイズを取得する | Programming Place Plus C言語編 逆引き

トップページC言語編逆引き

このページの概要 🔗

このページの解説は C99 をベースとしています

以下は目次です。

目的 🔗

存在しているファイルのサイズを取得したいとします。ただし、ファイルはオープンされていないものとします。

ここでは、ファイルサイズを long long int型(第19章)で扱うことにします。ただし、型の大きさが何ビットあるかと、使用する関数が何バイトまでの大きさのファイルに対応しているかは別問題であることに注意してください。

long long int型は C99規格で追加された、long よりも大きい整数型です。これが使えない環境では、long int型を使うか、処理系が独自で定義している大きな整数型を使うことを検討してください。

方法①(ファイルの末尾へ移動して、ファイルポジションを調べる)

よく紹介される手法は、fseek関数を使ってファイルの末尾まで移動し、その位置のファイルポジションを調べる方法です。

ファイルポジションを取得するには、ftell関数か、fgetpos関数を使います。

ftell関数は、結果を long int型で返します。 fgetpos関数は、結果を fpos_t型で返します(ポインタ型の引数経由で返します)。64ビット以上の大きさの整数型が扱える環境であれば、fpos_t型の方が大きい可能性が高いですが、明確な大きさは定められていません。

いずれにしても、この方法には以下のような理由で、移植性がありません

まず、ファイルがテキストモードでオープンされている場合、ftell関数や fgetpos関数が返す値がどんな意味なのかが未規定です。

バイナリモードの場合は、fseek関数の第3引数を SEEK_END にしたときの動作が保証されていません。

また、fgetpos関数で得られる値は、対になる fsetpos関数に渡す以外の用途で使える保証がありません。


次のサンプルプログラムでは、fgetpos関数を使っています。期待どおりに動作する可能性を高くするため、ファイルはバイナリモードで開くようにします。

#include <limits.h>
#include <stdio.h>

/*
    ファイルのサイズを取得する

    file_name: ファイルの名前
    戻り値:   ファイルのサイズをバイト単位で返す
              失敗した場合は -1LL を返す
*/
long long int get_file_size(const char* file_name)
{
    FILE* fp = fopen(file_name, "rb");
    if (fp == NULL) {
        return -1LL;
    }

    if (fseek(fp, 0L, SEEK_END) == 0) {
        fpos_t pos;

        if (fgetpos(fp, &pos) == 0) {
            fclose(fp);
            return (long long int)pos;
        }
    }

    fclose(fp);
    return -1LL;
}

int main(void)
{
    printf("%lld\n", get_file_size("test.txt"));
}

実行結果:

27

fpos_t型が素直な整数型であるという保証もありませんから、long long int型へのキャストが可能かどうかも不明です。

方法②(1文字ずつカウントする) 🔗

方法① はよく使われる方法であり、標準ライブラリ関数だけで実現できますが、移植性がありません。標準ライブラリ関数だけで実現できて、かつ移植性を高めるには、1文字ずつカウントしていくしかありません。ただしこの方法は、とてつもなく低速になる可能性があります。

#include <stdio.h>

/*
    ファイルのサイズを取得する

    file_name: ファイルの名前
    戻り値:   ファイルのサイズをバイト単位で返す
              失敗した場合は -1LL を返す
*/
long long int get_file_size(const char* file_name)
{
    FILE* fp = fopen(file_name, "rb");
    if (fp == NULL) {
        return -1LL;
    }

    long long int count = 0LL;
    for (;;) {
        if (fgetc(fp) == EOF) {
            if (feof(fp)) {
                break;
            }
            else if (ferror(fp)) {
                fclose(fp);
                return -1LL;
            }
            else {
                // EOF と同じ値をもつ有効な文字
            }
        }
        ++count;
    }

    fclose(fp);
    return count;
}

int main(void)
{
    printf("%lld\n", get_file_size("test.txt"));
}

実行結果:

27

方法③(stat関数を使う)[非標準] 🔗

非標準の関数ですが、stat関数(→参考。Man page of STAT)を使う方法があります。

#include <stdio.h>
#include <sys/stat.h>

/*
    ファイルのサイズを取得する

    file_name: ファイルの名前
    戻り値:   ファイルのサイズをバイト単位で返す
              失敗した場合は -1LL を返す
*/
long long int get_file_size(const char* file_name)
{
    struct stat st;

    if (stat(file_name, &st) != 0) {
        return -1LL;
    }

    // ファイルかどうか
    // S_ISREG(st.st_mode); の方がシンプルだが、Visual Studio では使えない。
    if ((st.st_mode & S_IFMT) != S_IFREG) {
        return -1LL;
    }

    return st.st_size;
}

int main(void)
{
    printf("%lld\n", get_file_size("test.txt"));
}

実行結果:

27

stat関数は、ファイルの状態を調べる関数です。ファイルに関するさまざまな情報を、stat構造体に格納してもらい、各メンバの値を確認することで、状態を調べられます。ファイルのサイズは、st_sizeメンバに格納されます。

stat関数は成功すると 0 を、エラー発生時には -1 を返します。 このサンプルプログラムでは、どんなエラーでも、ファイルは存在しないものとして扱っていますが、 errno を調べることで、エラーの詳細な内容を判定できます。 ただ、エラーの内容を知ったところで、stat構造体に値を取得できていない以上、 「判定できなかった」という結果を得る程度のことしかできません。

また、指定した名前が、本当にファイルの名前かどうかについても判定しています。stat関数は、たとえばディレクトリに関する情報を取得することもできるため、stat関数自体は成功しても、st_sizeメンバには、ファイルサイズが入っていないことがあります。

なお、st_sizeメンバの型は off_t型です。この型で表現できないほど巨大なファイルでは、正しい結果を得られません。off_t型がどんな型なのかは処理系によって異なりますが、Visual Studio では long int型の typedef になっているので、32ビットです。

Visual Studio の場合、stat関数の代わりに _stati64関数を、stat構造体の代わりに _stat64構造体を使うようにすれば、64ビットのファイルサイズを扱えるようになります。

方法④(_filelength関数を使う)[Windows] 🔗

Visual Studio に実装されている非標準の _filelengthi64関数(→参考)を使う方法があります。

#include <io.h>
#include <stdio.h>

/*
    ファイルのサイズを取得する

    file_name: ファイルの名前
    戻り値:   ファイルのサイズをバイト単位で返す
              失敗した場合は -1LL を返す
*/
long long int get_file_size(const char* file_name)
{
    FILE* fp = fopen(file_name, "rb");
    if (fp == NULL) {
        return -1LL;
    }

    long long int fsize = _filelengthi64(_fileno(fp));

    fclose(fp);
    return fsize;
}

int main(void)
{
    printf("%lld\n", get_file_size("test.txt"));
}

実行結果:

27

_filelengthi64関数は、<io.h> に宣言されています。

_filelengthi64関数の引数には、ファイル記述子(ファイルディスクリプタ)という値を指定します。 この値は、fopen関数でオープンして得られた FILEポインタを、_fileno関数に渡すと得られます。

_filelengthi64関数の戻り値が、ファイルサイズです。失敗した場合は -1L が返されます。

方法⑤(GetFileSize関数、GetFileSizeEx関数を使う)[Windows]

Windows API の GetFileSize関数(→Microsoft Docs)や GetFileSizeEx関数(→Microsoft Docs)を使う方法もあります。

これらの関数は、ファイルサイズを 64ビットの範囲で取得できます。ただし、GetFileSize関数の方は、上位32ビットと下位32ビットを分離して返してくるので、やや面倒です。64ビットの大きさが必要なのであれば、GetFileSizeEx関数を使う方が簡単でしょう。

ただ、これらの関数を使うのなら、fopen関数などのC言語の標準ライブラリ関数ではなく、CreateFile関数(→Microsoft Docs)のような、Windows API でファイルを操作しなければなりません。

以下のサンプルプログラムは、GetFileSizeEx関数を使用しています。

#include <limits.h>
#include <stdio.h>
#include <Windows.h>

/*
    ファイルのサイズを取得する

    file_name: ファイルの名前
    戻り値:   ファイルのサイズをバイト単位で返す
              失敗した場合は -1LL を返す
*/
long long int get_file_size(const char* file_name)
{
    HANDLE handle = CreateFileA(
        file_name,
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    if (handle == INVALID_HANDLE_VALUE) {
        return -1LL;
    }

    LARGE_INTEGER fsize;
    if (!GetFileSizeEx(handle, &fsize)) {
        CloseHandle(handle);
        return -1LL;
    }

    return fsize.QuadPart;
}

int main(void)
{
    printf("%lld\n", get_file_size("test.txt"));
}

実行結果:

27

GetFileSize関数や GetFileSizeEx関数を使用するには、Windows.h のインクルードが必要です。

GetFileSizeEx関数は、ファイルサイズを LARGE_INTEGER共用体(→Microsoft Docs)へ格納します。この共用体の目的は 64ビットの整数を扱うことですが、直接的に 64ビット整数として参照するための QuadPartメンバと、上位32ビットだけを参照するための HighPartメンバ、下位32ビットだけを参照するための LowPartメンバに分けられています。

今回は 64ビットで構わないので、QuadPartメンバの値をそのまま返すようにしています。

なお、CreateFile は実際にはマクロになっていて、UNICODEマクロの定義の有無によって、ANSI版(char型)の CreateFileA と、Unicode版(wchar_t型)の CreateFileW のいずれかに置換されます。 上のサンプルプログラムでは、文字列を const char* で扱っているため、CreateFileA の方を直接呼び出すようにしていますが、両対応できるのであれば、CreateFile を使うようにすれば良いです。


参考リンク 🔗


更新履歴 🔗

 「VisualC++」という表現を「VisualStudio」に統一。

 「サイズ」という表記について表現を統一。 型のサイズ(バイト数)を表しているところは「大きさ」、要素数を表しているところは「要素数」。

 文章中の表記を統一(bit、Byte -> ビット、バイト)

 新規作成。



逆引きのトップページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る