はじめに
glibで文字列の二次元配列を扱っていて、便利な関数がある!と思ったら盛大に使い方を間違った。
同じ過ちを、人類が二度と繰り返さないためのメモ。
問題に遭遇してから、解決までを時系列で書くので、g_strv_length()の使い方をまず知りたい人は、下の方から見てね。
g_strv_length()
guint g_strv_length (gchar **str_array);
Returns the length of the given NULL-terminated string array str_array.
Parameters:
str_array a NULL-terminated array of strings
Returns length of str_array.
ふむふむ、NULL文字で終端された文字列の配列を渡すと、その長さを返してくれるとな!
(実はこの時点で間違っているのだが、言い訳は後でやる)
やりたかったこと
read onlyなデータから、num個の文字列を読みだして、char型の二次元配列で色々処理したい。
とりあえず、自分の持ち物でないread_only_dataから、自分の持ち物であるstr_arrayに文字列をコピーしてくる。
gchar **str_array = g_new(gchar*, num);
for (gint i=0; i<num; i++) {
str_array[i] = g_strdup(read_only_data[i]);
}
これ以降、str_arrayをg_strv_length()に渡してあげれば、numが返ってくるはずだ!
C言語で快適文字列配列ライフを満喫するぞ!
とりあえず使ってみた結果
とりあえず、要素数が3のデータを用意してあげて、試してみる。
str_arrayにデータを詰めて、g_strv_length()の結果を見てみる。
(実際は、googletestでテストしたけど、再現が面倒なので省略)
g_printf("The length of array is %d\n", g_strv_length(str_array));
g_strfreev(str_array); // 使った後は開放しないとね!
これで、The size of array is 3と出力され・・・
どーん!
./strv
The length of array is 6
Segmentation fault (core dumped)
長さが6と出力された上、g_strfreev()でセグメントエラーになった…。
どうしてこうなった…。
改めてAPI仕様を眺める
Returns the length of the given NULL-terminated string array str_array.
怪しいのは、NULL-terminatedなのだが、文字列なので、NULL文字終端ってことだよなぁ…。
同じページのAPIリファレンス内で、文字列をコピーするg_stpcpy()もnull-terminated stringって書いてあるし。
わからん…。
gchar *g_stpcpy (gchar *dest, const char *src);
Copies a nul-terminated string into the dest buffer
分からん時はソースを読もう
OSSでわからん時はソースを読むのが手っ取り速い。
さっそくglibのソースコードを拾ってくる。
どうやら、gstrfuncs.cに実装されているらしい。
どれどれ。
guint
g_strv_length (gchar **str_array)
{
guint i = 0;
g_return_val_if_fail (str_array != NULL, 0);
while (str_array[i])
++i;
return i;
}
ってNULL文字終端の文字列配列じゃなくて、NULLポインタで終端されている文字列配列を期待しとるやないかーい!
念のため、strvを複製するg_strdupv()の実装を確認してみる。
i = 0;
while (str_array[i])
++i;
retval = g_new (gchar*, i + 1);
i = 0;
while (str_array[i])
{
retval[i] = g_strdup (str_array[i]);
++i;
}
retval[i] = NULL;
i+1の領域を確保して、最後にNULL詰めてるね、間違いないね。
正しい使い方
// NULL-termination requires +1 length
gchar **str_array = g_new(gchar*, num+1);
gint i = 0;
for (i=0; i<num; i++) {
str_array[i] = g_strdup("test");
}
str_array[i] = NULL; // NULL pointer terminated.
g_printf("The size of array is %d\n", g_strv_length(str_array));
g_strfreev(str_array);
無事動いた!
./strv
The size of array is 3
反省
同じページ内で、NULL文字終端とNULL pointer終端をどちらも、Null-terminatedと表現してあったので混乱した。
でも、よくよく考えると、NULL文字終端は、対象がa stringになっていたし、NULL pointer終端は、対象がarrayだった。
そもそも、NULL文字終端の文字列配列から、配列の長さを取るのって、無理じゃない?という警鐘が頭の中で鳴っていたのに、良く調べずに使おうとしたのが良くなかった。反省!
みなさんもお気をつけて!