LoginSignup
2
0

More than 5 years have passed since last update.

【C言語】glib g_strv_length()の使い方を盛大に間違えた話

Posted at

はじめに

glibで文字列の二次元配列を扱っていて、便利な関数がある!と思ったら盛大に使い方を間違った。
同じ過ちを、人類が二度と繰り返さないためのメモ。
問題に遭遇してから、解決までを時系列で書くので、g_strv_length()の使い方をまず知りたい人は、下の方から見てね。

g_strv_length()

g_strv_lenth API仕様

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って書いてあるし。
わからん…。

g_stpcpy API仕様

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文字終端の文字列配列から、配列の長さを取るのって、無理じゃない?という警鐘が頭の中で鳴っていたのに、良く調べずに使おうとしたのが良くなかった。反省!

みなさんもお気をつけて!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0