0と1、どちらから数えるか
Smalltalkの配列のインデックスは1から始まる、と本に書いてありました。また別の本によると、VB(Visual Basic)のCollectionも1ベースのようです。
現在のメジャーなプログラミング言語の多くでは、配列のインデックスは0から始まります。時々、プログラミング初学者から、なぜ0から数えるのですか、と質問されます。0から数えるのが習慣になってしまい、疑問すら抱かなくなっている人間には新鮮な疑問です。
なぜ0からか、には、全てのビットがゼロの値を無駄にしたくないから、と答えます。今となっては、昔からの慣習だから、もひとつの答えです。
8ビットで考えると、ビットがすべてゼロの値(プログラミング言語レベルでは数値0)をインデックスに使うか使わないかで、配列を256個までアクセスできるか(0から255)、255個までアクセスできるか(1から255)の差がでます。この1個の差は大きいので無駄にはできません。32ビットになれば、配列の先頭要素を捨てたところでたいして痛くもありませんが。
ここまでの話は、配列が連続したメモリ領域で、インデックスはメモリ領域内のオフセットに対応した値、という前提での話です。リンクリストなどは前提が異なるので、1から始めて悪いか、と開き直られると、別にいいんじゃない、としか答えようがありません。
1から数える、と言えば、Emacsのバッファ内のポイントがあります(参考 http://dev.ariel-networks.com/Members/sugawara/emacs-lisp-52c95f374f1a-30c330d530a1306830a630a330f330a67de8)。
Alan Kay(Smalltalkの作者)は初心者相手に媚を売ったのかもしれませんが(真相は不明)、rmsが初心者に媚を売ったとは思えません。何か理由があるのかも、と勘ぐりました。バッファの先頭バイトを何か別の用途に転用しているとか。勘ぐると、ありそうな気がしてきます。
結論から言うと、バッファの先頭バイトを別の用途に使ったりはしていません。先頭バイトを捨てているわけでもなく、(プログラマに見せる)ポイント値(1から始まる)から、内部のメモリ領域のオフセット(0から始まる)にするために、毎回、1を引いています。コードで言うと、次のような感じです(emacs-22.1から引用)。
/* editfns.c に point関数の実体 */ DEFUN ("point", Fpoint, Spoint, 0, 0, 0, doc: /* Return value of point, as an integer. Beginning of buffer is position (point-min). */) () { Lisp_Object temp; XSETFASTINT (temp, PT); return temp; } /* PTの定義はbuffer.hに以下 */ /* Position of point in buffer. The "+ 0" makes this not an l-value, so you can't assign to it. Use SET_PT instead. */ #define PT (current_buffer->pt + 0) /* PTの初期化はbuffer.c内のget-buffer-create関数内 */ DEFUN ("get-buffer-create", Fget_buffer_create, Sget_buffer_create, 1, 1, 0, 略) (name) register Lisp_Object name; { ...略 BUF_PT (b) = BEG; /* マクロBUF_PTとBEGはbuffer.hで以下のように定義 */ /* Position of point in buffer. */ #define BUF_PT(buf) ((buf)->pt) /* Position of beginning of buffer. */ #define BEG (1) #define BEG_BYTE (BEG)
マクロだらけで少し分かりづらいですが、elispレベルで (point) 関数を呼ぶと、内部的にはPTの値が返るだけです。このPTは最初に1(BEG)で初期化されています。これを見た時、内部的に確保したメモリ領域のオフセット1バイト目以降をバッファとして見せているのだと思いましたが、実際には次のようにBEG_BYTE(=1)を引いています。
/* Return the address of byte position N in current buffer. */ #define BYTE_POS_ADDR(n) \ (((n) >= GPT_BYTE ? GAP_SIZE : 0) + (n) + BEG_ADDR - BEG_BYTE)
マクロに隠蔽しているとは言え、この手のBEG_BYTEによる調整コードが意外にあるので、バグの元に見えて気持ち悪いです。
結局、ポイントを1ベースにするメリットはコード上からは全く分かりませんでした(デメリットはBYTE_POS_ADDRにあるような、REG_BYTEを引く処理)。謎です。
まったく別の話ですが、上のPTについた "+ 0" のテクニックは良いです。PT = 1のような代入式をコンパイルエラーにするための小細工です。+0 はコンパイルの最適化で消えるので、性能劣化はありません。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/from-zero-or-one/tbping