Personal tools
You are here: Home ブログ 井上 0と1、どちらから数えるか
« December 2010 »
Su Mo Tu We Th Fr Sa
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
Recent entries
Apache2.4のリリース予定は来年(2011年)初め(あくまで予定) inoue 2010-12-23
Herokuの発音 inoue 2010-12-20
雑誌記事「ソフトウェア・テストPRESS Vol.9」の原稿公開 inoue 2010-12-18
IPA未踏のニュース inoue 2010-12-15
労基法とチキンゲーム inoue 2010-12-06
フロントエンドエンジニア inoue 2010-12-03
ASCII.technologies誌にMapReduceの記事を書きました inoue 2010-11-25
技術評論社パーフェクトシリーズ絶賛発売中 inoue 2010-11-24
雑誌連載「Emacsのトラノマキ」の原稿(part8)公開 inoue 2010-11-22
RESTの当惑 inoue 2010-11-22
「プログラマのためのUXチートシート」を作りました inoue 2010-11-19
「ビューティフルコード」を読みました inoue 2010-11-16
Categories
カテゴリなし
 
Document Actions

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 はコンパイルの最適化で消えるので、性能劣化はありません。

The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/inoue/from-zero-or-one/tbping
Add comment

You can add a comment by filling out the form below. Plain text formatting.

(Required)
(Required)
(Required)
This helps us prevent automated spamming.
Captcha Image


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.