はじめての C

C programming note*1
検索文字列用の ファイルを つくって、そこに keyword を 収めるまでは わかったけど、そこから keyword を 取り出すのは どうするのか というと、
まず 外部変数として 下の 2つを 宣言しておきます。

#define MAX_KEY_COUNT 256 /* 検索語の最大数 */

char *keyword[MAX_KEY_COUNT];
key_n;

key_n が 検索文字列の 個数だと いうのは わかるだろう。 これは 簡単だ。

問題は keyword という 配列が 「char型を 指す ポインタ」 としてしか 宣言されていないことだ。 ポインタだというは、実は この配列は アドレスが 入っているだけの 配列だと いうことになる。

文字列の 実体は そのアドレスが 指し示す 別の場所に 存在しているのだ。 さて、その場所というのは どこに あるのだろう ? (p177)

それは、次の readkey() 関数の中で (こっそりと) 格納されています。

#define MAX_KEY_SIZE (40 + 1 + 1) /* 検索文字列の Byte数 */

int readkey(char **key, FILE *fp)
{
char buf[MAX_KEY_SIZE];
int n;

n = 0;
while (fgets(buf, MAX_KEY_SIZE, fp) != NULL) {
buf[strlen(buf) - 1] = '\0';
key[n++] = strdup(buf);
}
return n;
}

readkey() 関数の 仮引数は、後ろから 「検索文字列の 格納された ファイル」、それに 問題の まだ 中身のない 宣言されただけの ポインタ配列 keyword です (**key は だから *key[] のことです)。
つまり、文字列の 格納された ファイルを 開いて、この空の 配列 keyword に n個分の 文字列を (コピーし) 格納していくわけです。 詳しくいうと、

(ファイルを 読み込んだ後) ナル文字 '\0' を 代入しているところは、たいしたことは していない。 fgets() が 行末の 改行文字も 読んでしまうことを 知っていれば、何のために そうしているか わかるだろう。

各文字列から 改行文字 '\n' を 取り除いてるのですね。

重要なのは その次の行だ。 key という 配列の要素に strdup() という 関数からの 戻り値が 次々に 代入されている。

この関数が 文字列の 実体が 入っている アドレスを 返してくれるので、それを 配列の要素に 代入するだけで、最終的には (char型の データが) 配列 keyword に セットされることになる。(p177)

ここの ところです、

key[n++] = strdup(buf);
空の箱 (ポインタ配列) だけ 用意しておいて、後で 中身 (正確には そのアドレス) を 詰めていくのです。

strdup() 関数の 働きを 詳しく 述べる前に、重要なことが ある。 実は strdup() 関数は 標準ライブラリ関数には ない。 多くの 処理系で 採用されているが、どの場合も あるとは 限らない。(p177)

でも、たいていの場合 string.h に インクルードされています。

$ man strdup
として 調べてみると、次のように 書いてあります、
名前   strdup - 文字列を複製する

書式 #include
char *strdup(const char *s);

説明 strdup() 関数は、文字列 s の複製である
新しい文字列への ポインタを返す。

新しい文字列のためのメモリは malloc で
得ている。そして free() で開放すること
ができる。

返り値 strdup() 関数は 複製された文字列への
ポインタ、または 十分なメモリが確保でき
なかった場合には NULL を返す。

つまり、関数内部で malloc() を 使うことで、動的な メモリ割当が 実行されているのです。

変数や 配列で 静的 static に メモリ領域を 確保するのに対して、malloc() で 実行時に 領域を 確保することを 動的な メモリ割当 Dynamic Memory Allocation と 呼ぶ。

あらかじめ どれだけの メモリを 確保しておいたら よいか わからない場合などに べんりな しくみだ。

少なめに 準備しておいて 足りなくなるのも 困るが、めったにないような 「極端に 多く使う」 状況を 想定して ムダに 多くのメモリを 使うのも 避けたい ... などという場合に 使う。(p178)

また、普通は 「malloc() で 動的に 確保した メモリ領域は、使い終わったら free() 関数を 用いることで、そのメモリ領域を 開放」 しますから、そのための 関数も 用意しておきます。

void freekey(int n, char **key)
{
int i;

for (i = 0; i < n; i++)
free(key[i]);
}

最後に、find1 と 同じ 使い方も できるように するために、もう少しだけ コードを 改良していきます。 それには、もう一度 strdup() 関数を 使うことになりますが。

*1:「作ってわかる Cプログラミング」