怪奇? 「ファイルサイズ: 0」の謎

先週の課題プログラムには、「testfile」というファイルを作成して 256バイトのデータを書き込み、そのファイルの大きさが実際に何バイトかをstat システムコールを使って調べる、という内容が含まれていました。 しかし、実際にそのプログラムをMacOS Xの環境で実行すると、 プログラムの最後のstatシステムコールを使って調べた ファイルサイズの結果が「0」になるという不思議な現象が起こった人もいるかもしれません。 この謎を追ってみましょう。

まず、lsコマンドで実際のファイルサイズを調べてみます。

% ls -l testfile
-rw-------   1 furuse  prof  256 Apr 18 15:16 testfile
確かに、ファイルサイズは256バイトになっています。

次に、デバッガのgdbを使ってプログラムの実行状況を調べてみます(gdbの使い方は近日中に紹介します)。

(gdb) print stbuf.st_size
256

この結果を見ると、確かにstatシステムコールの結果、ファイルのサイズとして256というデータが取得できていることが確認できます。しかし、実際にプログラム中でprintfを使って

printf("File size: %d\n", stbuf.st_size);
というコードを書いてこれを実行すると、
File size: 0
となってしまいます。問題があるのはprintfの部分です。

とは言っても、長い間世界中の人たちが使っているprintf自体にバグがある可能性はほとんど0ですから、問題があるとすれば使い方の方でしょう。具体的には、正しい引数が与えられているかどうかが問題ということになります。そこで気になるのは、 第一引数の"%d"と、第二引数のstbuf.st_sizeのデータ型が一致しているかということです。 "%d"はint型のデータを出力するときに使います。stbuf.st_sizeのデータ型は何でしょうか。

MacOSのターミナルで

% man 2 stat
を実行すると、MacOSが提供しているstatシステムコールのマニュアルが表示されます。これをよく見てみると、
off_t    st_size;   /* file size, in bytes */
と書いてあります。つまり、st_sizeはoff_t型だということです。 このoff_tって、どういう型でしょう? int型と同じなのでしょうか?

st_sizeのデータ型を調べる方法はいくつかあるのですが、一番簡単なのはおそらくデバッガのgdbで調べることです。

(gdb) ptype stbuf.st_size
type = long long int
この結果を見ると、st_sizeのデータ型は「long long int」型だということがわかります。longよりさらに大きな値を扱える整数型です。

以下のプログラムを実行してみると、int型やlong long int型が何バイトの大きさなのかがわかります。

#include 

int main(int argc, char **argv)
{
        printf("int: %d\n", sizeof(int));
        printf("long long int: %d\n", sizeof(long long int));
}

実行結果は、以下の通りです。

int: 4
long long int: 8
これで、int型は4バイト、long long int型は8バイトだということがわかりました。

int型とlong long int型では、それぞれ「256」という数値はメモリ内でどのように表現されるのでしょうか。情報学類のMacの場合は、以下の通りです(16進表記であることに注意しましょう)。

int型
+----+----+----+----+
| 00 | 00 | 01 | 00 |
+----+----+----+----+

long long int型:
+----+----+----+----+----+----+----+----+
| 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 |
+----+----+----+----+----+----+----+----+

なお、この並びは実は使っているCPUによって変化します。興味のある人は「エンディアン」というキーワードで調べてみてください。実はこの用語、「ガリバー旅行記」に由来すると言われています。

さて、本論に戻ります。上のプログラムは以下のようになっていました。

printf("File size: %d\n", stbuf.st_size);
"%d"はint型の指定ですから、printfはst_sizeがint型だと思って処理します。 int型は4バイトです。ということは、printfは
long long int型:
+----+----+----+----+----+----+----+----+
| 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 |
+----+----+----+----+----+----+----+----+
の最初の4バイトだけを使って表示する数値を決めていることです。ファイルサイズが0になったのは、こういう理由なのでした。

正しい出力にするには、"%d"の部分をst_sizeの型に合わせる必要があります。 long long int型はこの部分を"%lld"とします。"d"の前は2つの「エル」です。

printf("File size: %lld\n", stbuf.st_size);

これで正確な値が表示されるようになります。

なお、4バイト(32ビット)で表現できる最大の正の整数は2^31 - 1で、 これだと2GBのファイルまでしか大きさを数えることができません (unsigned int型なら4GBまで)。最近のOSではより大きなファイルが扱えるよう、 ファイルサイズを表すために4バイトを超える大きさの整数型を使うのが 一般的になっています。また、このようにあとから必要に応じて バイト数を大きくできるように、statシステムコールではint型ではなくて off_t型を使っています(lseekも同様です)。