GNOMEnclature: GLib の驚異

C プログラミングを容易に

先月、George は、アプリケーションのコーディングに役立つ強力なツールである、glade と libglade について紹介しました。GLib は、C のプログラミングを劇的に容易にする、もう 1 つのユーティリティー・ライブラリーです。George が、その機能の概要を紹介しつつ、GLib の採用をお勧めします。

George Lebl, developerWorks columnist

George LeblGeorge (チェコ語では Jiri または Jirka) Lebl は、チェコスロバキアのプラハで生まれ、現在はカリフォルニア州サンディエゴに住んでいます。現在、学位を取るため鋭意努力中です。UNIX 以外のオペレーティング・システムを数年使用してから UNIX を使い始め、4 年ほど前から熱烈な UNIX マニアになりました。また、2 年ほど前からはフリー・ソフトのマニアでもあります。1997 年秋に GNOME プロジェクトに参加し、そのため C マニアにもなりました。最も重要なことは、彼が VI ユーザーであることです。



2000年 4月 01日

GLib は C 用のユーティリティー・ライブラリーです。C のプログラミングをもっと楽しめるものにしてくれます。以前からこの種のライブラリーは多数存在していますが、これほどまでにポピュラーで、焦点の定まった、一貫性のある、機能の多彩なライブラリーは、ほかにありません。GTK+/GNOME を通して GLib について学ぶ以前、私は主に C を使用していました。そして、C プログラマーとして、Perl や C++ (STL) などの他の言語が持つ、優れたコンテナーや一貫性のあるデータ・ストレージの機能性をうらやましく思っていました。残念ながら、C の標準ライブラリーの関数の中には、プラットフォームが変わると機能も変わる (あるいは特定のプラットフォームでしかインプリメントされていない)、矛盾した、低水準の関数が多数含まれています。

GLib は、そうした問題に答えるものです。C に関し、GLib は、C ユーザーがしばしば直面する、以下の 3 つの問題に対処します。

  • データ・コンテナー
  • 移植性
  • ユーティリティー

GLib の最も特筆すべき強力な性質は、そのコンテナーにあります。とは言え、GLib は、移植可能コードを書く面でも、非常に有用です。GLib を使えば、作成するコードの機能を、すべてのターゲット・プラットフォームで動作する小さなサブセットに制限する必要はなくなります。また、他のプラットフォーム上で機能をインプリメンテーションするにあたって、神頼みでなくても満足のいく結果を確実に出せるようになります。また、標準 C ライブラリーよりも一貫性のある (とは言え、似た性質を持つよう設計されている) ユーティリティー関数も揃っています。それらすべてに加え、GLib には簡単な字句解析機能と、イベント・ドリブンなアプリケーション用のメイン・ループ機能という、珍しい機能も備わっています。

型定義

GLib の型定義のセットは、広範囲の必要に応えるものです。タイプ入力の軽減に役立つように設計されたものもあれば、移植性に役立つ型定義もあります。また、コードの分かりやすさ、または命名の一貫性を高めるためのものもあります。タイプ入力の軽減用としては、unsigned タイプ用の型定義があります。unsigned int と入力する代わりに、guint と入力できます。これは、char 型を含め、すべての整数型にあてはまります。

移植性を考慮した型定義は、特定のサイズの整数を処理するという問題に対処するためのものです。たとえば、符号付き 16 ビット整数は gint16 になります。8 ビットの無符号整数は guint8 になります。サイズは、8、16、32、および 64 (マシンとコンパイラーが 64 ビット整数をサポートする場合) となります。これらを使用する場合は、G_HAVE_INT64 マクロが定義されていることを確認してください。

GLib には、コードをより分かりやすくするための基本的な型定義が 2 つ備わっています。1 つは gpointer で、これは void * です。もう 1 つは gboolean で、これは int の型定義です。後者はコードを読みやすくするためのものであり、自動コード・スキャナーが値の true/false を判別することを可能にします。また、これは機能性の問題というよりは好みの問題ですが、GLib ではすべての基本型に "g" という接頭部が付きます ("gint" など)。私個人はこうした型名を使用していませんが、使用した方が以前の型との整合性が取れると考える人もいます。


メモリーの割り振り

最初に、最も基本的なコンポーネントである、メモリーの割り振りについて取り上げます。標準の malloc と free も悪くありませんが、完全ではありません。たとえば NULL を渡すと、free はクラッシュし、malloc は NULL を戻します。戻り値を検査することを怠ると、アプリケーションが予期しない振る舞いをすることがあります。GLib には、それに対応する関数として g_malloc と g_free が定義されています。g_malloc は、新しいメモリーへのポインターを戻すことが保証されています。メモリーをこれ以上割り振ることができなくなると、プログラムは打ち切られます。これは、後で NULL ポインターを扱えなくなって segfault を受け取るよりは望ましいことです。g_free 関数には NULL を渡すことができますが、その場合、この関数は何もしません。これは、解放する必要のあるポインターを多数持つオブジェクトがある場合に、クリーナー関数の役割を果たします。

g_malloc も良くできていますが、g_new と g_new0 はさらに有用です。これらは良くできた簡単なマクロで、割り振るオブジェクトの型と、その数の両方を指定できます。さらに、g_new0 マクロは、メモリーをすべてゼロにセットします。この関数の長所は、ランダムなガーベッジがなく、すべてゼロであることが分かっている場合に、値を初期化していないことが原因で生じるエラーを見つけるのが容易になるということです。ここで、20 の整数から成る新しい配列を割り振り、後でこれを解放する例を見てみましょう。

配列の割り振りと解放
int *array = g_new0(int, 20);
  ...
  g_free(array);

ユーティリティー関数

C のストリングの処理には手間がかかります。C がストリングを処理する方法には、長所もあれば短所もあります。GLib のストリング・ユーティリティー関数を使えば、短所にも対処できます。まず、渡されたストリングを変更する関数をいくつか見てみましょう。大文字小文字の変換をする場合は、g_strup または g_strdown を呼び出し、ストリングを引き数として渡します。また、先行空白文字または末尾の空白文字を除去したい場合もあるでしょう。g_strchug はストリングの先行スペースを取り除き、g_strchomp は末尾のスペースを取り除きます。g_strstrip (実際にはマクロ) はその両方を行います。これらの 3 つの関数は、実際にはその引き数を戻します。この点には注意が必要です。これらの関数は新しいストリングを戻すわけではありません。引き数を戻すのは、関数同士を連結しやすくするためにほかなりません。たとえば、ファイルを読み取って、先行する空白文字をすべて除去し、各行を大文字にする場合の例を考えてみましょう。

FILE *fp = fopen("some-file.foo", "r");
  char buf[256];
  while(fgets(buf, sizeof(buf), fp)) {
          g_strup(g_strchug(buf));
          /* buf is now upper case and leading whitespace is gone */
  }

新しいストリングを割り振るための関数を調べてみましょう。g_strdup 関数は、標準の strdup 関数を、g_malloc を使ってインプリメントしたものに過ぎません。この関数はストリングを複製し、新しく割り振られたストリングへのポインターを戻します。複数の文字を複製したい場合には、g_strndup を使用します (ここでも、その数を示す整数の引き数を指定します)。もう 1 つの関数は g_strconcat です。この関数には、後ろに NULL が続くストリングの形の引き数を、任意の数だけ渡すことができます。すると、この関数はストリングを連結して、そのストリングが入った、新しく割り当てられたメモリーを戻します。たとえば、ストリングにディレクトリー名と拡張子を付加する関数を、次のように作成できます。

static void
  foo(char *string)
  {
          char *file;
          file = g_strconcat("/some/path/", string, ".txt", NULL);
          ...
          g_free(file);
  }

事実上 sprintf に置き換わる関数が 2 つありますが、それぞれまったく安全というわけではありません。1 つは g_snprintf です。これは snprintf 関数の移植可能なインプリメンテーションです。この関数の最初の引き数はバッファー、2 番目の引き数はバッファーのサイズです。その他の点では、この関数は sprintf とまったく同様に動作しますが、バッファーをオーバーランすることは決してありません。しかし、どれほどのバッファーで十分なのか分からない場合や、プリントする新しいストリングを割り振りたいという場合もあります。そのような場合には、g_strdup_printf を使用できます。この関数には、書式制御ストリングとその引き数だけを指定します。この関数は、プリントされたバッファーの入った、新しく割り振られたストリングを戻します。

g_snprintf と g_strdup_printf の使用例
char foo[256];
  char *bar;
  g_snprintf(foo, 256, "The number pi: %g", M_PI);
  bar = g_strdup_printf("The number pi: %g\n", M_PI);

当然のことながら、ほかにも多数の GLib ユーティリティー関数があります。完全なリストをご覧になりたい場合は、『参考文献』に挙げられている GLib の資料を参照してください。


コンテナー

基本的な点についてはすでに扱ったので、次にコンテナーについて調べてみましょう。ほとんどのコンテナーには 1 つの共通点があります。それは、保管するデータが void のポインターに過ぎないということです。これは柔軟性を高めることになりますが、コンパイラーによって強制される型の安全性は保証されないことになります。1 つの型には必ず 1 つのコンテナーを使うようにして、コンテナーを決して多重定義しないようにするか、実行時に型の検査を行う何らかの機構を使用するようにしてください。GLib の型が間違っていることが原因でコードにバグが混入したという経験は私にはないので、これは大きな問題ではないと思われます。void ポインターにまつわる問題の 1 つは、基本データ型を使用するのが難しいということです。しかしながら、私たちが思いつくようなプラットフォームであればどれでも (さらに重要なこととして、GLib が移植されているすべてのプラットフォームにおいて)、ポインターの占めるスペースは int の占めるスペースと同じ程度ですから、単にポインターの代わりに整数を保管するようにできます。GLib には、int から gpointer へ正確にキャストする GINT_TO_POINTER というマクロと、gpointer から int へ正確にキャストする GPOINTER_TO_INT というマクロが定義されています。これらのマクロを使用すれば、不必要な警告なしに、さまざまなプラットフォーム間で移植可能なコードを書くことができるようになります。例を挙げます。

 gpointer foo = GINT_TO_POINTER(50);
  ...
  int bar = GINT_TO_POINTER(foo);

リンク・リスト

リンク・リストは、GLib で最も頻繁に使用 (または乱用) されているコンテナーです。その構造上の名前は、GList と GSList です。GList はダブル・リンク・リストのインプリメンテーションであり、GSList はシングル・リンク・リストのインプリメンテーションです。これらのリストは、機能の点で大変良く似ています。ほとんどの場合は、GList を使用できます。余分のポインターを使用することに伴うスペース上の犠牲は大変小さなものに過ぎないからです。GSList には 2 つの関数が欠けていますが、それ以外は GList の関数とまったく同じです。GSList の関数の冒頭は、g_list_ ではなく、g_slist_ になります。

GList 構造体は、リスト全体へのポインターではなく、1 つのノードへのポインターに過ぎません。したがって、実際には、リスト内の各ノードが GList 構造体となります。各構造体には 3 つのポインターがあります。すなわち、"data" (このノードに保管されているデータへの gpointer)、"next" (次のノードへのポインター)、および "prev" (直前のノードへのポインター) です。当然のことながら、GSList には "prev" ポインターがありません。リンク・リストを保管するには、最初のノードの GList 構造体へのポインターを保管します。NULL ポインターは空のリストを示します。すべての GList の関数には、NULL を指定することができます。それらの関数は、NULL を空のリストとして正しく処理します。


基本的な関数

最も基本的な関数は、g_list_append と g_list_prepend です。これらの関数の最初の引き数は、現在のリストへのポインターです。2 番目の引き数は、そのリストの後ろまたは前に付加するデータです。これらの関数は、結果として生成されるリストの最初のノードへのポインターを戻します。たとえば、3 つのストリングから成るリストを作成して、それらのストリングを printf でプリントするには、次のようにします。

GList *list = NULL; /* our list pointer */
  GList *li; /* an iterator variable */
  list = g_list_append(list, "foo1");
  list = g_list_append(list, "foo2");
  list = g_list_append(list, "foo3");
  for(li = list; li != NULL; li = li->next)
          printf("STRING: %s\n", (char *)li->data);

リストを解放するには、g_list_free を呼び出します。この関数は、現在のノードだけではなく、リストのすべてのノードも解放します。しかしながら、リスト上の何らかのデータを解放するときには、プログラマー自身が処理を行うことが必要です。そのようなことを行うユーティリティー関数が 1 つあります。g_list_foreach という関数です。この関数の最初の引き数はリスト、2 番目の引き数は GFunc 型の関数ポインター、3 番目の引き数は user_data ポインター (後述) です。GFunc 自体、実際には 2 つの引き数を持ちます。すなわち、リスト内の単一のデータ値へのポインター、および g_list_foreach への最後の引き数として渡される user_data です。

ほとんどの場合、関数に user_data 引き数を渡すのではなく、単にすべてのノードのデータ・ポインターに対して g_free を呼び出したいと思われることでしょう。作成する関数が引き数を 1 つだけ (データ・ポインター) 取るのである限り、その関数を GFunc として渡すことができます。その関数は、余分の引き数を単に無視することになります。しかし、その後で、その関数を GFunc にキャストする必要があります。キャストを行うということは、自分が行っていることには自分で責任を持つと C に言っていることになりますから、型の異なる関数を渡さないよう、注意が必要です。たとえば、すべてのノードに対して g_free を呼び出して、後からリストを解放するには、次のようにします。

 g_list_foreach(list, (GFunc)g_free, NULL);
  g_list_free(list);

リスト内の特定のノードが必要になる場合があります。たとえば、リストの n 番目の GList ポインターを取得することが必要だとします。g_list_nth を呼び出し、引き数としてリスト・ポインターと整数 n を渡すと、この関数は GList ポインターを戻します。しかし、ほとんどの場合は、g_list_nth_data を使用する方を望まれるでしょう。これは、そのノードのデータ・ポインターを戻します。これらの関数は両方とも、エラーの場合 (インデックス番号が無効な場合など) に NULL を戻します。では、あるデータ・ポインターのインデックスを取得するにはどうしたらよいでしょうか。g_list_index を呼び出し、これにリストと検索するデータ・ポインターを指定すると、この関数はそのようなデータ・ポインターを持つ最初のノードのインデックスを、整数の形で戻します。リスト内の特定の GList ポインターのインデックスを検索するには、g_list_position を呼び出し、データ・ポインターの代わりに GList ポインターを渡します。どちらも、エラーの場合は -1 を戻します。インデックスはどれも、C の標準どおり、ゼロを基準としたものになります。例を挙げます。

GList *list = NULL;
  char *foo = "foo";
  char *bar = "bar";
  list = g_list_append(list, foo);
  list = g_list_append(list, bar);
  /* test g_list_nth_data */
  printf("This should be bar: '%s'\n", (char *)g_list_nth_data(list, 1));
  /* test g_list_index */
  printf("This should be 0: '%d'\n", g_list_index(list, foo));

リスト内のあるエレメントを検索する必要が生じることがあります。関数 g_list_find を呼び出し、引き数としてリストとデータ・ポインターを指定すると、そのようなデータ・ポインターを持つノードの GList ポインターを戻します。しかし、ポインターがまったく異なっていても、実データは同一であることが少なくありません (たとえばストリングの場合)。この場合、カスタム比較が必要になります。これは、g_list_find_custom によって行うことができます。これは g_list_find によく似ているものの、余分の引き数を取ります。それは、GCompareFunc 型の関数ポインターです。この関数は引き数として 2 つのポインターを必要とし、整数を戻します。2 つのポインターが等しい場合、整数は 0 になります。最初のデータ・ポインターが 2 番目のポインターより小さい場合は負数になります。最初のデータ・ポインターが 2 番目のポインターより大きい場合は正数になります。これは、標準の strcmp 呼び出しと同じセマンティクスです。したがって、ストリングには strcmp を使用してください。しかし、GCompareFunc は通常 2 つの void ポインターを取るのに対し、strcmp は 2 つの文字ポインターを期待するので (これは事実上同じことですが)、キャストを行うことが必要になるでしょう。

g_list_sort を使用してリストをソートする場合にも、GCompareFunc を使用できます。これは、引き数としてリストと比較関数を取り、新しいリストのポインターを戻します。

GCompareFunc の使用例
GList *list = NULL;
  GList *list2;
  list = g_list_append(list, "foo");
  list = g_list_append(list, "bar");
  list2 = g_list_find_custom(list, "bar", (GCompareFunc)strcmp);
  /* list2 should now point to the second node of the list */
  list = g_list_sort(list, (GCompareFunc)strcmp);
  /* list should now be sorted */

その他のいくつかの関数についても知っておく必要があります。たとえば、g_list_reverse はリストを反転し、g_list_copy はすべてのノードをコピーします (データはコピーしません。データはプログラマー自身がコピーする必要があります)。また、g_list_last と g_list_first もあります。これらの関数は、リストの任意のノードから見て最初および最後のノードを検索します。これらの関数はすべて GList ポインターを引き数とし、GList ポインターを戻します。


別のコンテナー: GString

別の種類のコンテナーについて調べてみましょう。GString は、サイズ変更の可能な、ストリングのコンテナーです。任意のデータのコンテナーではありません。これは標準 C ストリングよりも上位のレベルで機能しますが、内部バッファー (これは通常の C ストリングに過ぎません) を常に利用できます。そのため、GString は C ストリングを作成する上で、非常に有用なものとなります。GString 構造体には、str と len という 2 つのエレメントがあります。str は内部メモリー・バッファーを指すに過ぎません (このバッファーは、直接に修正することはできないものの、他の C ストリングと同様に使用できます)。len はストリングの長さを示す整数です。

新しい GString を作成するには、g_string_new 関数を呼び出します。この関数は、GString を初期化するために使用する C ストリングを引き数として取ります。NULL を渡すと、空ストリングであるとみなします。g_string_new に NULL を渡す場合であっても、GString 構造体の 'str' エレメントは必ず有効な C ストリングになり、このエレメント自体が NULL になることは決してありません。ストリングを解放する場合は、g_string_free を呼び出します。この関数は、意外なことですが、引き数を 2 つ必要とします。すなわち、GString ポインターと、内部メモリーを解放するかどうかを指定するブール値です。ストリングを完全に除去する場合は TRUE を渡します。内部メモリーを通常の C ストリングとして生かしつつ GString 構造体を解放する場合は、FALSE を渡します。以下に、GString を使用して C ストリングを作成する方法を示します。

GString を使用したコード例
static char *
  build_a_string(void)
  {
          GString *gstring;
          char *tmp;
          /* make a new string */
          gstring = g_string_new(NULL);
          /* here we build the string somehow */
	  ...
          /* store the pointer to the internal string buffer */     tmp = gstring->str;
	  /* free the string, but not the internal string buffer */
          g_string_free(gstring, FALSE);
	  /* return the new string */
          return tmp;
  }

このようなストリングを作成する方法について調べてみましょう。その前に、大多数の GString 関数は GString ポインターを戻すということに注意してください。これは、それらの関数が新しいストリング構造体を戻すという意味ではありません。それらの関数は、指定したのとまったく同じポインターを戻します。同一のポインターを戻すことにより、いくつかの関数を連結することや、それに類似した他の技法を用いることが可能になります。ほとんどの場合、これはあまり便利なものではなく、戻り値は無視してしまって差し支えありません。

基本的な操作として、後ろに付加する操作と前に付加する操作があります。それぞれの操作には 2 つの種類があります。すなわち、C ストリングを追加するという操作と、単一の文字を追加するという操作です。g_string_append には、GString ポインターと C ストリングを指定します。この関数は、GString の後ろに この C ストリングを付加します。一方、g_string_append_c には、2 番目の引き数として単一の文字を指定します。この関数はその文字だけを付加します。g_string_prepend と g_string_prepend_c は前述の関数と同様に動作しますが、GString の後ろではなく、前にデータを付加します。

後ろに付加する操作と前に付加する操作
GString *gstring = g_string_new("foo");
  g_string_append(gstring, "bar");
  g_string_append_c(gstring, '!');
  g_string_prepend_c(gstring, '!');
  /* gstring->str should now contain "!foobar!" */

ストリングや文字を別のストリングに挿入したい場合があります。これは、g_string_insert と g_string_insert_c で行えます。これらの関数は、3 つの引き数を必要とします。すなわち、GString ポインター、挿入位置 (ゼロを基準とする)、および挿入する内容です。

Using g_string_insert_c

  GString *gstring = g_string_new("foobar");
  g_string_insert_c(gstring, 3, '-');
  /* now gstring->str should be "foo-bar" */

ストリングの一部を消去する必要が出てくることがあるかもしれません。これは、g_string_erase で行えます。この関数には、(GString ポインターに加えて) 開始位置を示す整数と、消去するセクターの長さを指定します。

g_string_erase の使用
GString *gstring = g_string_new("fooBLAHbar");
  g_string_erase(gstring, 3, 4);
  /* now gstring->str should be "foobar" */

次に、大変良くできた関数を説明します。GString 用の sprintf 風の関数です。これには、2 つの種類があります。1 つは g_string_sprintf で、これは sprintf と同様に機能し、ストリングを上書きします。もう 1 つは g_string_sprintfa で、これはストリングを上書きせずに付加します。付加するという操作は、何かのリストを作成する場合などに、大変便利です。1 ~ 10 までの数字のコンマ区切りのリストを作成する例を考えてみましょう。

g_string_sprintfa の使用
int i;
  GString *gstring = g_string_new(NULL);
  for(i = 1; i<= 10; i++) {
          if(i > 1)
                  g_string_sprintfa(gstring, ",%d", i);
else
                  g_string_sprintfa(gstring, "%d", i);
  }

他にも、ストリングを全部大文字または小文字にする関数 (g_string_up と g_string_down)、ストリングを特定の文字数に切り捨てる関数 (g_string_truncate。関数の 2 番目の引き数として文字数を指定)、GString に新しい C ストリングを割り当てる関数 (g_string_assign) があります。

g_string_assign の使用
  GString *gstring = g_string_new("fooBAR");
  /* make all upper case */
  g_string_up(gstring);
  /* make all lower case */
  g_string_down(gstring);
  /* truncate to 3 characters */
  g_string_truncate(gstring, 3);
  /* assigning "string" to the string */
  g_string_assign(gstring, "string");

GLib について書くべきことは他にもたくさんあり、このページには収まり切りません。ハッシュ、ツリー、関係、字句スキャナー、メイン・ループ、ファイル記述子入出力などについて論じた記事に関心がおありの方は、私までご連絡ください。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=228246
ArticleTitle=GNOMEnclature: GLib の驚異
publish-date=04012000