ftell()/fgetpos()を使うならバイナリモードで!テキストモードでftell()/fgetpos()を使ってはまった件

ftell()の不可解な動作にはまってしまいました。また同じ間違いを繰り返さないようにメモ。

『ファイルを読み込みながらftell()で読み込んだ位置を保存、fseek()でそこに戻る』

なんていうテキスト処理をVisual Studio Express 2015で作ってたんですが、なぜかftell()した位置に戻れない。

どうやら、読み込むファイルによってはftell()で返ってくる値がおかしなことになるようです。

テストコード

現象再現用のテストコード作ってみました。コンパイル環境はVisual Studio Express 2015です。

#include <stdio.h>

void test(const char* pFile, const char* pMode)
{
    FILE* fp = fopen(pFile, pMode);

    char buf[256];
    fgets(buf, sizeof(buf), fp);

    long pos = ftell(fp);
    printf("%s, %s : pos = %d\n", pFile, pMode, pos);

    fclose(fp);
}


int main(void)
{
    test("test1.txt", "r");
    test("test2.txt", "r");

    test("test1.txt", "rb");
    test("test2.txt", "rb");

    return 0;
}

読み込むテキストファイルはこんな感じ。

test1.txt

test2.txt

両方とも同じ内容に見えますよね。1行目は全く同じですが、2行目がちょっと違います。

test1.txtは1行目も2行目も改行コードは\r\n(いやゆるWindows改行コード)です。 test2.txtは1行目は\r\nで、2行目は\n(いわゆるLinux改行コード)です。

それぞれをバイナリエディタで表示したのがこちら。

テストコードは1行目を読み込んだ時点のftell()結果を表示するものです。

test1.txtもtest2.txtも1行目は同じなので、同じ値になってほしいところですが実行結果はこうなりました。

test1.txt, r : pos = 7
test2.txt, r : pos = 6
test1.txt, rb : pos = 7
test2.txt, rb : pos = 7

test2.txtをテキストモードで開いたときだけ値が違ってますね。

いや、2行目を読み込んだ後なら違いが出てるもの分かるんですよ。でも、1行目はtest1.txtもtest2.txtも同じなんです。なんでこんな結果になるんだろう?

ftell(),fgetpos()を使うならバイナリモードで!

どうやら、改行コードが\r\nと\nが混在しているファイルをテキストモードで開いてftell()するとおかしくなるみたいです。

バイナリモードで開けば読み込むファイルの内容にかかわらず正常な値が得られるようですね。

ということで、ftell()/fgetpos()はバイナリモードで使うのがよさそうです。

原因はよく分かりません。Visual Studio Express 2015のライブラリソースを読めばわかるんでしょうけど、めんどくさいのでそういうものだと割り切ります。興味ある方はソースを読んでみてください。

Visual Studio Express 2015のダウンロード方法

Visual Studio Express 2015をダウンロードしたい方はこちらの記事を参考にしてください。

eng-notebook.com