プログラミング言語 C

hash関数による テーブル参照が わからないので、訳しながら 読んでみる。

このセクションでは、構造体の より特色のある面の 説明のために、table-lookup package の 中身を 書くことにしよう。

そのコードには、マクロや コンパイラにおける symbol table management に 特徴的な ルーティンが 見出される。 例えば、#define での statement を 考えてみよう。

#define IN 1

という line が 見つかると、name "IN" と それに 置き換わる text "1" とが 1つの table に 格納される。

その後、この statement の中で name "IN" が でてくると

state = IN;

"1" と 取り換えられる。

(このように) name と 置き換える text とを 処理するには、2つの ルーティンが (必要になる)。

install(s, t) によって、name "s" と それを置き換える text "t" とを 1つの table に 記憶させる。 ここでの s と t とは 文字列である。

lookup(s) では、table から "s" を 検索し、探しだすと その place の ポインタを 返し、なければ NULL を 返す。

この algorithm を hash-search と いい、入ってくる name は 符号がつかない 小さな整数へと 変換され、それが ポインタ配列の index として 使われている。

配列の element は、それの hash値をもつ name が 記された block の リンクリストの 初め (beginning) を 指している。 hash値をもつ name が ない (element は) NULL である。


(element) (block) (block)
・-----> ・ -----> 0 (list)
0 ・→ ・→ name
0 ・→ ・→ defn

・-----> 0
0 ・→ name
・→ defn

list の中の block は 構造体であり、name と text それに 次の block に 繋げる next への (それぞれの) ポインタから なっている。 next が NULL ポインタを 指すと、それは list の 終わりを あらわす。


struct nlist { /* table entry */
struct nlist *next; /* next entry in chain */
char *name; /* defined name */
char *defn; /* replacement text */
};

ここでは ポインタ配列が (次のように 使われている)。

#define HASHSIZE 101

static struct nlist *hashtab[HASHSIZE];

lookup および install で 使われる hash 関数は、文字列の中の 各文字の value を 混ぜ合わせながら 加算して、さらに array size で 割って その余り (modulo) を (戻り値として) 返している。

これは 最良の algorithm では ないが、しかし 短いし 効果的である。

/* hash: form hash value for string s */

unsigned hash(char *s)
{
unsigned hashval;

for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
}

hash値が マイナスに ならないことは unsigned 演算によって 保証されている。

hash処理によって、配列 hashtab に list の starting index が つくられる。 文字列が 見つかると、そこから 始まってる block の list に 含まれている (ことになる)。

検索は lookup によって 行なう。 lookup では、その entry が すでに 含まれていれば そのポインタを (戻り値として) 返し、なければ NULL を 返す。

/* lookup: look for s in hashtab */

struct nlist *lookup(char *s)
{
struct nlist *np;

for (np = hashtab[hash(s)]; np != NULL; np = np->next)
if (strcmp(s, np->name) == 0)
return np; /* found */
return NULL; /* not found */
}

lookup での for ループは、リンクリストを たどっていくときに よく使われる やり方である。

for (ptr = head; ptr != NULL; ptr = ptr->next)

install では、lookup を 使って その name が すでに 含まれているかどうかを 調べている。 もしあれば 新しい定義が 古いそれと 入れ替わる。 そうでなければ 新しい entry を 作成する。

また install では、何らかの理由で 新しい entry を つくる 余地がないときは NULL を 返すようにする。(p174-176)

struct nlist *lookup(char *);
char *strdup(char *);

/* install: put (name, defn) in hashtab */

struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;

if )((np = lookup(name))( == NULL) { /* not found */
np = (struct nlist *)malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashbval] = np;
} else /* already there */
free((void*)np->defn); /* free previous defn */
if )((np->defn = strdup(defn))( == NULL)
return NULL;
return np;
}

う〜ん、ややこしいヨ ... (;_;)