ファイルのバイト位置

C/C++ではカウンターや変数の値は0から起算するし、配列などの添え字も0から起算する。たとえば、配列の先頭のデータは ary[0] という具合である。また、ビットフィールドなどでも 0 bit から 7 bit までというように「0起算」である。

ところで、ファイルのバイト位置についてはどうか。「0起算」でいくのか「1起算」なのか。これがいまだによくわからない。個人的には(私の場合ではと同義)、初めてC言語を覚えたときは慣例的に「0起算」でやっていたように思う。途中からBASICとの関連で「1起算」に変えて、現在までそのようにしている。どちらも大した根拠があるわけではない。しかし、おそらく「0起算」でいくのが統一的でいいのだろうとは思う。

ところで、Harvey M.Deitel & Paul J.Deitel「C++プログラミング第2版Vol.2」(ピアソン・エデュケーション)248pに次のような記述がある。
次の文
 inClientFile.seekg(0);
は、inClientFileと結合しているファイルの先頭(バイト位置0)にファイル位置ポインタを再設定します。
- Harvey M.Deitel & Paul J.Deitel「C++プログラミング第2版Vol.2」(ピアソン・エデュケーション)248p -
この記述では、ファイルの先頭のバイト位置は0である、というふうに読める。これを「0起算」説ということにしておこう。


比較


ファイルの「オープン直後」と「先頭に移動時」に「0」となっているのは、ファイルのバイト位置が「0」という意味ではない。

ファイルの先頭からのオフセット(先頭からどれだけ離れているかという値)が「0」であるという意味である。
これはfseek関数とftell関数の仕様をみれば明らかである。


「0起算」では実感とは少し違和感があるのが問題である。どちらが実感にフィットするかといえば「1起算」のほうである。
また、このファイルのファイルサイズは39バイトである。「1起算」でいけばファイルの末尾のデータが39バイト目ということになり、両者の数値が一致する。これが「0起算」でいくとすると末尾のデータが38バイト目ということになり、これにもやや違和感を与える。



BASICの場合

3バイト目から読み込む場合は次のようになる。BASICではファイルの先頭バイト位置は明らかに「1起算」である。
ファイル内の最初のレコードまたはバイトの位置は位置 1、次のレコードまたはバイトの位置は位置 2 となり、これ以降も同様です。
- Microsoft公定ヘルプ Excel 2013 or later 「Get」の項 -
SAMPLE
Seek #nFile, 3
この方法はUTF16_SJIS関数など多数のFunctionで使っている。


C/C++の場合

上のBASICと同じことはCでは次のようにしている。この方法はunicode.cppのReadFilePos関数など多くの部分で使っている。
SAMPLE
fseek( stream, 2, SEEK_SET );
BASICの場合が3で、ここが2だからといって、これは「0起算」だというわけではない
C/C++では、ファイルの先頭を1バイト目と呼ぼうが、0バイト目と呼ぼうが、そんなこととは無関係に処理ができるのである。
なお、fseek関数の「2」は「(0起算での)2バイト目」という意味ではない。先頭のバイト位置が0であろうと1であろうと5であろうと、そんなこととは無関係にこれが使えるのである。
要するに、C/C++ではファイルの先頭バイト位置が何であるか(0か1かなど)は知らなくてもかまわないのである。そんなものはプログラミングでは使わないからである。


関数
このトピックに関係が深い関数は次の二つである。
fseekでは具体的に指定する数値(第2引数)は、先頭バイト位置が1であろうと0であろうと、それとは無関係である。
ftellの戻り値も、先頭バイト位置が1であろうと0であろうと、それとは無関係である。


結局どうなの

物理的に存在するある特定のファイルについて、人間側の都合でこれを、先頭から「1バイト目,2バイト目,3バイト目,...,39バイト目」と数えても、「0バイト目,1バイト目,2バイト目,...,38バイト目」と数えても、別にどちらであっても問題はないわけである。それで個人的には最近は「1起算」でやっているというだけである。

以前BASICなどをバカにしていた(そんなものは使ったこともない)時代はたいてい「0起算」でやっていたように思う。
しかし、最近Excel/VBAでバカ相手に「ごっこネタ」を扱うようになってから、BASICでは「1起算」だということで、それに合わせたという経緯がある。それで、こういう表示は先頭バイトが1になるようにしてきた。
C言語ではBASICと違って、ファイルの絶対的なバイト位置を直接指定して操作することはない。したがって、その表示が「0起算」であろうが「1起算」であろうが、そんなことはどうでもいいことなのである(と思う)


先頭から3バイト離れた位置にあるから「3」という数字になっているということである。
ファイルのバイト位置の数え方を0からにするか1からにするかということとは関係がない。


- 2015/12/05 -



C++の関数リファレンスなどによれば、seekgには「入力ポインタ」という記述しかないが、seekpには「出力ポインタ」で「ファイルストリームではファイルの先頭からのバイトオフセット位置」という記述がある。seekpの場合はCの場合に近いことになる。
しかし、seekgの場合はファイルの絶対的なポインタの位置(バイト位置)で、seekpの場合はファイルの先頭からのバイトオフセット位置だというのは何かアンバランスなものを感じる。
これはMicrosoft「Visual C++ ランタイムライブラリリファレンス」(アスキー)による。streamposとstreamoffはlongと同義である(とtypedefされている)。








ReadFilePos@utf16_function.cpp(ccp_0304)



オフセット(offset)という考えは一般的になじみが薄いことがこの問題の根底にある。
「岸から3番目の石の上にいる人はどちら」という言い方に慣れているからである。


「1起算」では[A]になるが、「0起算」では[B]になる。
「岸から3個離れたところの石の上にいる人はどちら」という言い方はほとんどの人はしない。
オフセット(offset)でいけば、「1起算」「0起算」には関係なく[B]になる。


タコの殿堂
こんなことに興味をもつのはエクセルバカだけね。バカは能もないのに「バイト」という語が大好きなのよ。