glib: 移植性と実用性 glib は、UNIXシステム互換機及びウインドウズ上で Cの移植性ある実用的ライブラリです。 この章では GTK+ や Gnome アプリケーションで最も標準的に使われているライブラリの特徴を いくつか解説します。 glib は単純で、概念的にもよく知られています。ですから、ここでは簡単にすませましょう。 glib のより詳しい説明は、glib.h またはライブラリに付属するフリー glibリファレンスマニュアルをご覧下さい。 (話ははずれますが、glib、GTK+、もしくは Gnomeヘッダファイルの利用に関しては 躊躇しないで下さい。それらは非常に簡潔で読みやすくクイックリファレンスとして便利です。 関連して、もし実装について非常に特殊な疑問があるようでしたら、 ぜひソースコードを見て下さい。) glib の様々な機能は、一貫性のあるインタフェースになることを意図しています。例えば、コーディング 形式は半オブジェクト指向で、識別子は一連のネームスペース作成のため "g" を先頭に 伴っています。 glib には、シングルヘッダファイル glib.h が存在します。 基本 glib は、標準的によく使われる C言語構文の置換を行います。 この章では、glibの 基礎的なタイプ定義、マクロ、メモリ割り当てルーチン、ユーティリティ関数の配列 について解説します。 タイプ定義 glib は、Cの標準タイプ (int, long, etc.) を用いるのではなく、 それ独自の定義を行います。それらは、様々な用途に適しています。 例えば、gint32 は、標準の Cタイプでは 確実にできない、32ビット幅を保証できます。 guint は、単純ですが unsigned よりタイプにするのが容易です。 整合性のみを考えた typedef (タイプ定義)は少数ですが存在します。 例えば、gcharは、常時通常の char と同等です。 以下の従来的タイプは glib で定義されています。 gint8, guint8, gint16, guint16, gint32, guint32, gint64, guint64—これらは保証サイズの整数値を 表しています。全てのプラットフォームが、64ビットの整数を提供するという わけではありません。つまりプラットフォームが、それらを必要とするならば、 glib は、G_HAVE_GINT64 を定義 しなければなりません。(簡単にいうと、 guint タイプは unsigned で、 gintタイプは signed です。) C では boolタイプが存在しないので、 gboolean はコードを更に読みやすく させるのに役立ちます。 gchar, gshort, glong, gint, gfloat, gdouble は、全くの装飾です。 gpointer は、 void * よりもタイプに対して都合が いいかもしれません。gconstpointer は、const void* を与えます。 (const gpointer は、概して 予想される処理を行いません。理由が明確でないよう でしたら、Cの優れた参考書をご覧下さい。) よく使われるマクロ glib は、多くの Cプログラムで用いられる数々の一般的なマクロを定義しています。 で示されています。 これら全てが自明であるべきです。 MIN()/MAX() は、その引数の中で最小値及び最大値を返します。 ABS() は、その引数の絶対値を返します。 CLAMP(x, low,high) は、 x が [low,high] の範囲内ならば x を意味します。 つまり、x が範囲より小さければ low を返し、 x が範囲より大きければ high を返します。 下記に示すマクロに加え、 TRUE/FALSE/NULL は、通常通り 1/0/((void*)0)と定義されています。
よく使われる C のマクロ #include <glib.h> MAX a b MIN a b ABS x CLAMP x low high
glib に特有な多くのマクロも存在します。例えば、下記 のような移植可能な gpointer-to-gintgpointer-to-guint の変換です。 多くの glib のデータ構造は、gpointer に格納されるようになっています。 動的に割り当てられたオブジェクトにポインター を格納したいというのは正当です。しかしながら、 時には動的に割り当てる必要なく、 単純な整数列の格納を要することもあるでしょう。 Cの規格ではそれを厳密に保証していませんが、 gint もしくは guintgpointer変数への格納は glib が組み込まれたプラットフォームといった幅広い範囲で可能です。 もちろん場合によっては、それに対するなんらかの媒体が必要になるでしょう。 下記 のマクロでは、 仲介媒体の表記を表しています。 具体例です。 gint my_int; gpointer my_pointer; my_int = 5; my_pointer = GINT_TO_POINTER(my_int); printf("We are storing %d\n", GPOINTER_TO_INT(my_pointer)); 注意点は、確かにこれらのマクロはポインタで整数の格納を可能にしますが、 整数でポインタを格納することはできません。 このような移植を行うには、ポインタを long に格納しなければなりません。 (そうすることは、間違いなくよくないことなのですが。)
ポインタで整数を格納するためのマクロ #include <glib.h> GINT_TO_POINTER p GPOINTER_TO_INT p GUINT_TO_POINTER p GPOINTER_TO_UINT p
デバッグのマクロ glib には、コードで不変値や選定条件を実装できるような便利なマクロが存在します。 GTK+ は、これらのライブラリを用いています。—理由の一つは、 非常に安定していて使いやすいからです。それら全てが、 G_DISABLE_CHECKS もしくは G_DISABLE_ASSERT を定義すると 消滅します。つまり、できたコード上のパフォーマンスの低下はありません。 これらのライブラリを用いることは、非常に良い考えです。これを使うと、 より速くバグを見つけられるでしょう。以後のバージョンで再現性のないバグを 発覚したときはどんな場合でも、アサーションマクロ(assertion) 及びチェックマクロ(check)を加えることさえ可能です。 —これは、回帰的組合せを補充します。 チェックは、特に書いているコードが、ほかのプログラマにブラックボックスとして 扱われる場合、特に役立ちます。つまりユーザは、どんなときどのようにそのコードを 使い間違えたか直ちに分かるのです。 もちろん、正確的に機能に対してデバッグのみのステートメントに わずかながらも依存していないと確実性をもてるよう注意しなければ なりません。生産コードで消滅するステートメントは、 副作用を起こしてはいけません
前提条件のチェック #include <glib.h> g_return_if_fail condition g_return_val_if_fail conditionretval
は、glibの 前提条件チェックを示しています。 g_return_if_fail() は、警告を出力し、condition偽(FALSE)ならば直ちに現行の関数から 戻ります。 g_return_val_if_fail() は、 似ていますが、retvalを返すことが できます。 これらのマクロは、驚くほど便利です。—頻繁に用いれば、 とりわけGTK+の実行時タイプチェックと併せることで、 不正なポインタやタイプエラーを発見するのに費やす時間を半減する でしょう。 これらの関数を用いるのは簡単です。 これは、glibのハッシュテーブルの実装例です。 void g_hash_table_foreach (GHashTable *hash_table, GHFunc func, gpointer user_data) { GHashNode *node; gint i; g_return_if_fail (hash_table != NULL); g_return_if_fail (func != NULL); for (i = 0; i < hash_table->size; i++) for (node = hash_table->nodes[i]; node; node = node->next) (* func) (node->key, node->value, user_data); } チェックを省き、パラメータとしてNULL をこの関数に渡すと不可思議なセグメンテーションフォールトといった 結果になるでしょう。 ライブラリを用いている場合は、どこでエラーが起こったかデバッガを 使ってはっきりさせなければならないでしょう。 何が悪いのかを見つけるには、glibのコードまで掘り下げないと いけないかもしれません。 チェックを使うとNULL引数が 認められていないと伝わり、正当なエラーメッセージが出されます。
アサーション #include <glib.h> g_assert condition g_assert_not_reached
glibは、更に従来のアサーションマクロも用意してあります。 をご覧下さい。 g_assert() は、 基本的に assert() と同一ですが、 G_DISABLE_ASSERT に応答し、 どのプラットフォームでも一貫性をもって稼働します。 g_assert_not_reached()も提供されて おり、これは常に失敗するアサーションです。 アサーションは、プログラムを終了するため abort() をコールし、 (ご自分の環境がサポートしているならば) デバッグの役割としてコアファイルを作り出します。 フェイタルアサーションは、関数及びライブラリの 内部整合性をチェックするのに用いられるべきです。 g_return_if_fail()は、 正しい値がプログラムモジュールの公的インタフェースに確実に 渡されることを意図されています。 つまり、アサーションが失敗したら概してアサーションを含むモジュール上 の不具合を探します。 g_return_if_fail()が、失敗を チェックすると、大概モジュールを起動するコード上で不具合を 探すでしょう。 glibのカレンダ計算モジュールではこのコードは、相違点を表しています。 GDate* g_date_new_dmy (GDateDay day, GDateMonth m, GDateYear y) { GDate *d; g_return_val_if_fail (g_date_valid_dmy (day, m, y), NULL); d = g_new (GDate, 1); d->julian = FALSE; d->dmy = TRUE; d->month = m; d->day = day; d->year = y; g_assert (g_date_valid (d)); return d; } 始まりの前提条件チェックは、日、月、年に対して適した値にユーザが 渡しているかを保証します。終わりのアサーションは、glibが正しい オブジェクト、つまり与えられた正しい値を構成したとの確証です。 g_assert_not_reached() は、 "不可能な"状況を印すのに用いられるべきです。 共通の用途は、列挙法の全て可能な値を処理するわけではない switch文を発見する点です。 switch (val) { case FOO_ONE: break; case FOO_TWO: break; default: /* 不当な列挙法値 */ g_assert_not_reached(); break; } デバッグ用のマクロ全てが、glibのg_log()機能を 用いて警告を出力します。 つまり警告は、元来のアプリケーション名及びライブラリを含み、 それゆえ任意に警告-出力ルーチンの置換を備えられるのです。 例えば、全警告をコンソール上で出力する代わりにダイアログボックス、 またはログファイルに残すことも可能です。
メモリ glib は、標準malloc()free()を独自のg_ 変形を加え、g_malloc()及び g_free()の形で含んでいます。 をご覧下さい。 これらは、数々の小さな点で優れています。 g_malloc() は、いつも gpointerを返し、決してchar*を返しません。 つまり、戻り値をキャストする必要性がないのです。 g_malloc() は、malloc() の基礎的な部分が失敗していたら異常終了します。 それゆえ、NULL戻り値を チェックする必要はありません。 g_free()は、 NULLを返すことで 0といった sizeを上手く処理します。 g_free() は、渡したどのような NULL ポインタをも無視します。 このちょっとした利点に加え、g_malloc()g_free()は、様々な種類のメモリデバッギングや プロファイリングをサポートできます。 --enable-mem-checkオプションを glibの設定スクリプトに渡すと、コンパイルされた g_free()は、同じポインタを解放する際、 警告を与えるでしょう。 --enable-mem-profileオプションは、 メモリの使用統計を保つコードを有効にします。 つまり、g_mem_profile()をコールすると コンソール上に出力されます。 最終的には、USE_DMALLOCを定義でき、 glibのメモリ確保には、MALLOC() 等を使用します。デバッギングマクロは、同プラットフォーム上 dmalloc.hで有効です。
glib メモリ割り当て #include <glib.h> gpointer g_malloc gulong size void g_free gpointer mem gpointer g_realloc gpointer memgulong size gpointer g_memdup gconstpointer memguint bytesize
g_malloc()g_free()が 通常のmalloc()にはfree()が 対応していることが重要です。(もし C++ をお使いなら) newに対して deleteです。 さもなければ、最悪の事態が生じるでしょう。というのも、 この割り当てが異なるメモリプールを使用するかもしれないからです。 (そして、new/ deleteは、構成関数と消去関数を コールします。) 当然g_realloc()は、realloc() と同等です。更に便利なg_malloc0()も存在し、 それによって配置されたメモリは、ゼロクリアされます。 g_memdup()は、 memで始まっているbytesizeバイトのコピーを返します。 g_realloc()g_malloc0()は、 g_malloc()との整合性のため、 共に0といったsizeを受け入れます。 しかしながら、g_memdup()は、受け入れません。 次のこと: g_malloc0()は、ビットすら設定 されていないメモリを扱っているということ が明確でないならば、そこに配置しようとしているどのようなタイプに対して 値0ではなくなります。 時に、0.0に初期化された浮動小数点 の配列が得られると予測する人がいますが、このようには動作 しません 最後に、型-認識の割り当てマクロもあります。 をご覧下さい。 これら各々へのtype引数は型名で、 count引数は、 割り当てのtype-サイズブロック数です。 これらのマクロは、多少のタイプ入力や増殖の余地を残してくれるので、 エラーの生じる頻度が減ります。 また、自動的にターゲットポインタ型にキャストするので、 そこで割り当てられたメモリを誤った種類のポインタに当てようとすると コンパイラ警告をトリガすべきでしょう。 (これは、警告を出すようにしてあればですが、信頼性のあるプログラマは そうするでしょう!)
割り当てマクロ #include <glib.h> g_new typecount g_new0 typecount g_renew typememcount
文字列処理 glib は、文字列処理用の関数を多数提供しています。 中にはglib特有なものもや移植性への問題を解決するものもあります。 これらは全てglibのメモリ割り当てルーチンで、上手く内的操作を 行っています。 さらに、gchar*より優れた文字列に 焦点を当てるとGStringタイプも存在 します。本書は、それに関しては対象としていませんがドキュメントは http://www.gtk.org/から得られます。
移植性のラッパ #include <glib.h> gint g_snprintf gchar* bufgulong nconst gchar* format ... gint g_strcasecmp const gchar* s1const gchar* s2 gint g_strncasecmp const gchar* s1const gchar* s2guint n
は、glibが一般に実装している 置換の代表です。しかしながら、ANSI Cへの拡張移植は可能ではありません。 C の面倒な点の一つは、クラッシュの原因、セキュリティホール発生といった 通常好ましくないsprintf()を供給することですが、 比較的安全で広く実装されているsnprintf()は、 ベンダー拡張の一つです。 g_snprintf()は、 本来のsnprintf()の機能もそれを伴っている プラットフォーム上では含んでおり、伴っていないプラットフォームでは 実装します。 そのため絶対的にsprintf()を使用しないことも 可能です。 多少よくなっているといえても、 従来的にsnprintf()は、そのバッファを NULL終了する保証はありません。 g_snprintf()は、確実です。 g_strcasecmp()g_strncasecmp()は、 2つの文字列の大文字、 小文字を区別せず実行します。任意で最大長を用いることもできます。 strcasecmp() は、 多数のプラットフォームで使用可能ですが、一般的ではありません。 代わりに、glibを用いるのは賢明です。 の関数は、文字列を適当に修正 します。はじめの2つは、それぞれ文字列を小文字及び大文字に変更し、 g_strreverse()は、その文字の大きさを逆にします。 g_strchug()g_strchomp()は、 文字列を"chug"(先置スペースを削除)し、 一方で"chomp"(後置スペースを削除)します。 これら最後の2つは、文字列を適当に修正しつつ返します。 場合によっては、戻り値を用いる方が便利なこともあるかもしれません。 マクロも存在します。g_strstrip()は、 先置、後置スペースを共に取り除く機能を兼ね併せていますが、 単に個々の機能として用いられます。
適した文字列の修正 #include <glib.h> void g_strdown gchar* string void g_strup gchar* string void g_strreverse gchar* string gchar* g_strchug gchar* string gchar* g_strchomp gchar* string
は、glib が補っている いくつかの半標準関数を示しています。g_strtodは、 ユーザのデフォルトロケールで変換に失敗した場合 "C"ロケールでダブルに 変換を行なおうとする例外はありますが、 strtod()— 文字列nptrをダブルに変換 —のような感じです。 *endptrは、最初の非変換文字、 つまり数値形式後のどんなテキストにでも設定されます。 変換に失敗した場合*endptrは、 nptr に設定されます。 endptr は、 無視されるようにさせるため、NULL のこともあります。 g_strerror()g_strsignal() は、その拡張子g_ がない関数 と類似しますが、移植可能です。(それらは、errnoとして文字列形式、またはシグナルナンバーを 返します。)
文字列変換 #include <glib.h> gdouble g_strtod const gchar* nptrgchar** endptr gchar* g_strerror gint errnum gchar* g_strsignal gint signum
は、文字列割り当てに関する glibの豊富な機能の配列です。驚くようなことではありませんが、 g_strdup()g_strndup() は、割り当てられたstrのコピー、 もしくは最初のstrn文字を生成します。 整合性のためglibメモリ割り当て関数を用いると、 NULLポインタが渡されると NULLを返します。 printf()の変形は、フォーマットされた文字列を 返します。 g_strescape は、その引数であらゆる \文字をそれらの前に別の \を挿入することでエスケープし、 エスケープ文字列を返します。 g_strnfill()は、 fill_charで埋め込まれた サイズlengthの文字列を返します。 g_strdup_printf()には、特に述べておくべき点 があります。それは、コードのこの共通部分を処理するのにより容易な方法 ということです。 gchar* str = g_malloc(256); g_snprintf(str, 256, "%d printf-style %s", 1, "format"); このように宣言する代わりに、ブートにバッファの正確な長さを換算する 必要性がなくなります。 gchar* str = g_strdup_printf("%d printf-style %s", 1, "format");
文字列の割り当て #include <glib.h> gchar* g_strdup const gchar* str gchar* g_strndup const gchar* formatguint n gchar* g_strdup_printf const gchar* format ... gchar* g_strdup_vprintf const gchar* formatva_list args gchar* g_strescape gchar* string gchar* g_strnfill guint lengthgchar fill_char
glib は、文字列を連結するのに適した関数を提供します。 をご覧ください。 g_strconcat()は、引数に並んでいる それぞれの文字列を連結することで作成される新しく割り当てられた文字列 を返します。最後の引数は、NULL でなければなりません。そこで、g_strconcat()は、 いつ停止すべきかを知るのです。 g_strjoin()は似ていますが, separatorがそれぞれの文字列間に 挿入されます。separatorが、 NULLの場合は、separatorは 用いられません。
文字列の連結 #include <glib.h> gchar* g_strconcat const gchar* string1 ... gchar* g_strjoin const gchar* separator ...
最後に、は、 NULL-文字列の最終配列を 処理するルーチンです。g_strsplit()は、 各々のdelimiterstringを中断し、新たに割り当てられた 配列を返します。g_strjoinv()は、 任意にseparatorを用いて、 配列にそれぞれの文字列を連結し、割り当てられた文字列を返します。 g_strfreev()は、配列でそれぞれの文字列を 解放し、それゆえ配列自身も解放します。
処理方法 <structname role="C">NULL</structname>-最終文字列ベクトル #include <glib.h> gchar** g_strsplit const gchar* stringconst gchar* delimitergint max_tokens gchar* g_strjoinv const gchar* separatorgchar** str_array void g_strfreev gchar** str_array
データ構造 glib は、共通のデータ構造を多数実装しているので、リンクリストを 必要とするたびごとに、適した展開方法を再開発する必要はありません。 この章は、glibのリンクリスト、ソートされたバイナリ系ツリー、 多進系ツリー、そしてハッシュテーブルの実装についてです。 リスト glib は、通常のシングルおよびダブルリンクリストを、それぞれ GSListGListで提供します。 これらは、gpointerのリストとして 実装されています。 つまり、GINT_TO_POINTER及び GPOINTER_TO_INTマクロを用いて、 整数を確保することができます。 GSListGList は、g_list_previous() 関数は存在し、g_slist_previous()は存在しない といった例外はありますが、同一のAPIです。 この章ではGSListについて記述して いますが、すべてダブルリンクリストにも適用します。 glibの実装では、空のリストは単なる NULLポインタです。それは、長さ0の 有効なリストなので、常時安全にNULL をリスト関数に渡します。リストを作成し、一つの要素を追加するコードは、 次のようになります。 GSList* list = NULL; gchar* element = g_strdup("a string"); list = g_slist_append(list, element); glib リストは、Lisp の影響を強く受けています。つまり空のリストは、 それが理由で特別に"nil"値になります。 g_slist_prepend()は、 cons—新しいセルをリスト の前に追加する定期時刻操作のように頻繁に動作します。 注意すべき点は、リストのヘッドが変った場合、戻り値を使ってリストを修正 する関数に渡されるリストに置き換えなければならないということです。 glibは、必要に応じてリストリンクの割り当て解除や割り当てをしながら メモリの問題を処理します。 例えば、以下のコードは上記に追加された要素を取り除き、 リストを空にします。 list = g_slist_remove(list, element); list は、現在NULLです。 もちろん、まだ自分自身でelement を解放しなければなりません。 全てのリストをクリアするには、g_slist_free()を 使用してください。これは、一度に全てのリンクを取り除きます。 g_slist_free()は、戻り値を取りません。というのは、 常にNULLだからです。それゆえ、 好みによって簡単にその値をリストに割り当てられます。 明らかにg_slist_free()は、リストのセルだけを 解放するのであって、そのリストコンテンツでどのような処理が行なわれるか 知る手法はありません。 リストの要素にアクセスするには、 直接GSList struct に明記します。 gchar* my_data = list->data; リストの繰返しには、コードを次のように書くといいでしょう。 GSList* tmp = list; while (tmp != NULL) { printf("List data: %p\n", tmp->data); tmp = g_slist_next(tmp); } は、 GSListコンテンツを変更するための 基本的な関数を示しています。 これら全てに対して、リストのヘッド変更時にはリストポインタに戻り値を 割り当てなければなりません。注意すべき点は、glibはリストの最後に ポインタを格納しません。 そのためフリペンドは、定期時刻操作であり、追加、挿入、削除 は、リストのサイズに比例します。 とりわけ、これはg_slist_append()を用いてリストを 作成することがとんでもない考えであることを 意味します。もし、特別な順序でアイティムが必要ならば、 g_slist_prepend()を用いて、その後 g_slist_reverse()を呼び出します。 リストに、頻繁に追加する可能性があるならば、最後の要素をポインタに しておくこともできます。以下のコードは、効率的に追加を実行する のに用いられています。 void efficient_append(GSList** list, GSList** list_end, gpointer data) { g_return_if_fail(list != NULL); g_return_if_fail(list_end != NULL); if (*list == NULL) { g_assert(*list_end == NULL); *list = g_slist_append(*list, data); *list_end = *list; } else { *list_end = g_slist_append(*list_end, data)->next; } } この関数を用いるには、リストとその後尾をどこかに格納し、それらの アドレスをefficient_append()に渡します。 GSList* list = NULL; GSList* list_end = NULL; efficient_append(&list, &list_end, g_strdup("Foo")); efficient_append(&list, &list_end, g_strdup("Bar")); efficient_append(&list, &list_end, g_strdup("Baz")); もちろん、list_endをアップデート せずにリストの最後を変更する可能性のあるリスト関数を使用しないよう 注意しなければなりません。
リンクリストコンテンツの変更 #include <glib.h> GSList* g_slist_append GSList* listgpointer data GSList* g_slist_prepend GSList* listgpointer data GSList* g_slist_insert GSList* listgpointer datagint position GSList* g_slist_remove GSList* listgpointer data
リスト要素にアクセスするために、 の関数が提供されます。 これら全てが、リストの構造を変更しません。 g_slist_foreach()は、 GFuncをリストのそれぞれの要素に あてはめます。GFuncは、次のように 定義されています。 typedef void (*GFunc)(gpointer data, gpointer user_data); g_slist_foreach()で用いられると、GFuncは、list 内の各list->dataでコールされ、 g_slist_foreach()に提供されたuser_dataを渡します。 g_slist_foreach()は、Scheme の "map" 関数に 相当します。 例えば、文字列のリストがあり、その文字列に応じた変換で並列リストを 作成可能にする必要があるかもしれません。 これらは、比較的単純な例からの引用で efficient_append()関数を用いているコードです。 typedef struct _AppendContext AppendContext; struct _AppendContext { GSList* list; GSList* list_end; const gchar* append; }; static void append_foreach(gpointer data, gpointer user_data) { AppendContext* ac = (AppendContext*) user_data; gchar* oldstring = (gchar*) data; efficient_append(&ac->list, &ac->list_end, g_strconcat(oldstring, ac->append, NULL)); } GSList* copy_with_append(GSList* list_of_strings, const gchar* append) { AppendContext ac; ac.list = NULL; ac.list_end = NULL; ac.append = append; g_slist_foreach(list_of_strings, append_foreach, &ac); return ac.list; } glib 及び GTK+ は、"関数ポインタやユーザデータ" の用語を 多量に使用します。 関数プログラミング的な経験があるようでしたら、 これがクロージャを作成するためラムダ式を使用する ような感じです。(クロージャは、関数を環境 と組み合わせます—名前-拘束値のセットにします。この場合、"環境" は、append_foreach()に渡すユーザデータで、 "クロージャ"は、関数ポインタとユーザデータの組合せです。)
リンクリストでデータにアクセス #include <glib.h> GSList* g_slist_find GSList* listgpointer data GSList* g_slist_nth GSList* listguint n gpointer g_slist_nth_data GSList* listguint n GSList* g_slist_last GSList* list gint g_slist_index GSList* listgpointer data void g_slist_foreach GSList* listGFunc funcgpointer user_data
手軽なリスト-操作のルーチンも存在し、 に示されています。 g_slist_copy()の例外を除き、これら全てがリストの 適した部分に反映します。つまり、戻り値を割り当て、ポインタに 渡された値についてはそのままにしておかなければならないということです。 リスト要素を追加または除去する時のようにです。 g_slist_copy()は、新たな割り当てリストを返すので、 両方のリストを継続して使えますが、結局は両リストを解放しなければ ならないでしょう。
リンクリストの処理 #include <glib.h> guint g_slist_length GSList* list GSList* g_slist_concat GSList* list1GSList* list2 GSList* g_slist_reverse GSList* list GSList* g_slist_copy GSList* list
ソートリストのために のような関数が備わっています。これらを使用するためには、 GCompareFuncを明記しなければ なりません。これは、ちょうど標準Cの比較関数qsort() に類似します。glibの形式では次のようになります。 typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b); a < bの時、関数は負の数を返し、 a > bの時は正数値を、 a == bならば、0を返します。 比較関数を用いると、既にソートされているリストにある要素を挿入したり、 リスト全体をソートしたりできます。リストは、昇順にソートされます。 g_slist_find_custom()を用いると、 リストの要素を検索する際GCompareFunc を再利用することができます。 (注意:glibではGCompareFuncは、 矛盾した用いられかたをされます。glibは、 時にqsort()-型関数の代わりになり、同等の動作を 期待します。しかしながら使用は、リストのAPI 範囲内で一貫性をもっています。) ソートリストには注意が必要です。というのは、誤用すると急に 効率が悪くなるからです。 例えばg_slist_insert_sorted()は、 O(n)オペレーションですが、ループに多数要素を挿入させるために用いると ループが爆発的な頻度で実行します。単純に全ての要素をプリペンドし、 g_slist_sort()を呼んだ方がいいでしょう。
ソートリスト #include <glib.h> GSList* g_slist_insert_sorted GSList* listgpointer dataGCompareFunc func GSList* g_slist_sort GSList* listGCompareFunc func GSList* g_slist_find_custom GSList* listgpointer dataGCompareFunc func
ツリー glibには、2種類のツリーが存在します。 GTreeは、基本的なバランスのとれた バイナリツリーで、キーによってソートされ、 キー-値を格納する際役立ちます。GNode は、任意のツリー構造化されたデータ、例えば分析ツリーまたは分類を格納 します。 GTree GTreeを作成かつ破棄を行なうには、 に示されているような 作成-削除の組合せを用います。 GCompareFuncは、 GSListに記述されている qsort()-型比較関数と同様です。 この場合、ツリーでキーを比較するのに用いられます。
バランスバイナリツリーの 作成と破棄 #include <glib.h> GTree* g_tree_new GCompareFunc key_compare_func void g_tree_destroy GTree* tree
ツリーのコンテンツを処理するための関数は、 に示されています。 全てがシンプルです。g_tree_insert()は、 既に存在している全ての値を上書きします。存在している値が、 割り当てられたメモリへの唯一のポインタである時は注意してください。 g_tree_lookup()が、キー検索に失敗すると NULLを返すか、関連値を返します。 キーも値も共にタイプgpointer をもっていますが、GPOINTER_TO_INT()GPOINTER_TO_UINT()マクロは、 その代わりに整数を使用することもできます。
<structname role="C">GTree</structname> コンテンツ処理 #include <glib.h> void g_tree_insert GTree* treegpointer keygpointer value void g_tree_remove GTree* treegpointer key gpointer g_tree_lookup GTree* treegpointer key
ツリーがどれほどの大きさであるかといった概念をもたらす2つの関数が に示されています。
<structname role="C">GTree</structname> のサイズ決定 #include <glib.h> gint g_tree_nnodes GTree* tree gint g_tree_height GTree* tree
g_tree_traverse() () を用いると、 ツリー全体をたどることができます。それを使用すると、 GTraverseFuncを提供し、それには 各々のキー-値ペアとg_tree_traverse()に与えた data引数が渡されます。 GTraverseFuncが、 FALSEを返すと全探索は継続し、 TRUEが返されると全探索が終了 します。値を用いてツリーを検索する際にも使用できます。 これは、GTraverseFuncの定義です。 typedef gint (*GTraverseFunc)(gpointer key, gpointer value, gpointer data); GTraverseType を列挙すると、 4つの可能な値があります。以下は、 GTreeに関する意味です。 まずG_IN_ORDER は、ノードの 左の子 (GCompareFuncに従う "下の"キー)を見て、それから現ノードのキー-値の組に探索関数を コールし、それから右の子を行ないます。この探索は、低い方から 高い方へとGCompareFuncに 従って順次行なわれます。 G_PRE_ORDER は、現ノード のキー-値の組に探索関数をコールし、それから左の子に移り、 右の子へと移動します。 G_POST_ORDERは、左の子に移り それから右の子、そして最終的に現ノードのキー-値の組に 探索関数をコールします。 G_LEVEL_ORDERは、 GNodeに対してのみ意味を もちます。GTreeでは、 用をなしません。
<structname role="C"> GTree</structname>の探索 #include <glib.h> void g_tree_traverse GTree* treeGTraverseFunc traverse_funcGTraverseType traverse_typegpointer data
GNode GNodeはN-法ツリーで、 親と子リストをダブルリンクリストとして実装されています。 それゆえ、リスト操作の大部分が、 GNode API で相似体です。 また、様々な方法でツリーをたどることもできます。 以下はノードの宣言です。 typedef struct _GNode GNode; struct _GNode { gpointer data; GNode *next; GNode *prev; GNode *parent; GNode *children; }; GNodeにアクセスするマクロが存在し、 に示されています。 GListを用いた場合のように、 dataメンバは、 直接使われるでしょう。 これらのマクロは、それぞれnextprevchildrenメンバを返します。 更に、引数がNULLかどうか 非参照とする前にチェックし、 その場合はNULLを返します。
<structname role="C"> GNode</structname>メンバにアクセス #include <glib.h> g_node_prev_sibling node g_node_next_sibling node g_node_first_child node
ノードを作成するのに便利な_new()関数が 提供されています()。 g_node_new() は、 dataは含めますが、 子及び親ノードを含まないノードを作成します。とりわけ、 g_node_new()は、ルートノード作成の場合のみ 用いられます。 というのは、必要に応じて自動的に新しいノードを作成する便利な マクロが提供されているからです。
<structname role="C"> GNode</structname>の作成 #include <glib.h> GNode* g_node_new gpointer data
ツリーをビルドするためには、 に示されているような 基本的操作がとられます。各々の操作が、直接追加されたノードを 返します。 便宜上ループを記述またはツリーに再帰するとします。 GListと異なり、確実に戻り値を 無視します。
<structname role="C"> role="C">GNode</structname>ツリーのビルド #include <glib.h> GNode* g_node_insert GNode* parentgint positionGNode* node GNode* g_node_insert_before GNode* parentGNode* siblingGNode* node GNode* g_node_prepend GNode* parentGNode* node
に示されている、 便利なマクロが基本的操作に実装されています。 g_node_append()は、 g_node_prepend()と類似します。 その他は、data引数を取り、 そのため自動的にノードを割り当て、適した基本操作をコールします。
<structname role="C"> GNode</structname>のビルド #include <glib.h> g_node_append parentnode g_node_insert_data parentpositiondata g_node_insert_data_before parentsiblingdata g_node_prepend_data parentdata g_node_append_data parentdata
ツリーからノードを取り除くには、 に示されている 2つの関数があります。g_node_destroy()は、 ツリーからノードや全子ノードを破棄して、ノードを削除します。 g_node_unlink()はノードを取り除き、 そこにルートノードを作ります。つまり、独立しているツリーにサブツリー を備えます。
<structname role="C"> GNode</structname>の破棄 #include <glib.h> void g_node_destroy GNode* root void g_node_unlink GNode* node
GNodeツリーの上位下位を見分ける 2つのマクロが、に 示されています。ルートノードは、親及び同胞を全く伴わないノード として定義されています。リーフノードは、子を持ちません。
<structname role="C"> GNode</structname>の重要点 #include <glib.h> G_NODE_IS_ROOT node G_NODE_IS_LEAF node
GNodeについての役立つ情報を 報告するようglibに問うことができます。というのは、その ルートノード、深さそして特別なデータポインタを含むノードの数を 備えているからです。 そのような関数が に示されています。 GTraverseType は、 GTreeに関連して、 先に紹介されていますが、 これらはGNodeに対する可能な値です。 G_IN_ORDER は、まず 最も左のノードの子に移り、それからそのノード自身を訪れ、 そして残りのノードの子に移動します。 これはあまり役には立ちません。 大概、GTreeを伴った 用途を意図しています。 G_PRE_ORDERは 現ノードを訪れ、順次子に移っていきます。 G_POST_ORDERは、順次各子を 再帰し、現在のノードを訪れます。 G_LEVEL_ORDERは、 まずノード自体を訪れ、それから各ノードの子、そして子の子、 さらに子の子の子などと移っていきます。つまり、各深さ0のノード、 そして各深さ1のノード、更に各深さ2のノードのように訪れます。 GNode ツリー探索関数は、 GTraverseFlags引数を持ちます。 これは、探索の性質を変更するのに用いられるビットフィールドです。 現在は、3つのフラグのみ存在します—リーフノードのみ、または 非リーフノード、もしくは全ノードを訪れることができます。 G_TRAVERSE_LEAFSは、 リーフノードのみ探索します。 G_TRAVERSE_NON_LEAFSは、 非リーフノードのみ探索します。 G_TRAVERSE_ALLは、単なる (G_TRAVERSE_LEAFS | G_TRAVERSE_NON_LEAFS)のショートカットです。
<structname role="C">GNode</structname> プロパティ #include <glib.h> guint g_node_n_nodes GNode* rootGTraverseFlags flags GNode* g_node_get_root GNode* node gboolean g_node_is_ancestor GNode* nodeGNode* descendant guint g_node_depth GNode* node GNode* g_node_find GNode* rootGTraverseType orderGTraverseFlags flagsgpointer data
GNode関数を残すのは簡単で、 その大部分が単なるノードリストの子上の操作です。 は、 それを示しています。GNode に特有な2つの関数タイプ宣言があります。 typedef gboolean (*GNodeTraverseFunc) (GNode* node, gpointer data); typedef void (*GNodeForeachFunc) (GNode* node, gpointer data); これらは、訪れているノードへのポインタと提供するユーザデータで コールされます。GNodeTraverseFunc は進行の過程で探索を停止するとTRUE を返します。それゆえ、値によってツリーを検索するのに g_node_traverse()と組み合わせ GNodeTraverseFuncを用いることが 可能です。
<structname role="C">GNode</structname>へのアクセス #include <glib.h> void g_node_traverse GNode* rootGTraverseType orderGTraverseFlags flagsgint max_depthGNodeTraverseFunc funcgpointer data guint g_node_max_height GNode* root void g_node_children_foreach GNode* nodeGTraverseFlags flagsGNodeForeachFunc funcgpointer data void g_node_reverse_children GNode* node guint g_node_n_children GNode* node GNode* g_node_nth_child GNode* nodeguint n GNode* g_node_last_child GNode* node GNode* g_node_find_child GNode* nodeGTraverseFlags flagsgpointer data gint g_node_child_position GNode* nodeGNode* child gint g_node_child_index GNode* nodegpointer data GNode* g_node_first_sibling GNode* node GNode* g_node_last_sibling GNode* node
ハッシュ テーブル GHashTableは、 単純なハッシュテーブルの実装ですが、連想配列に定期的巡閲を提供します。 ハッシュテーブルを用いるためには、 GHashFuncを供給しなければなりません。 その関数は、ハッシュキーを渡されたときに正整数を返します。 typedef guint (*GHashFunc) (gconstpointer key); guint(剰余テーブルのサイズ)を返した それぞれの値は、ハッシュの "スロット"または"バケット"に一致します。 GHashTableは、各スロットでキー-値の リンクリストを格納することで衝突を回避します。そのため、 GHashFuncで返された guint値は、可能な guint値の組合せをできる限り均一に 分散させなければなりません。さもなければ、ハッシュテーブルは リンクリストに陥ってしまいます。 GHashFuncは、 高速でなければなりません。というのは、各巡閲で用いられるからです。 GHashFuncに加え、同等にキーを テストするにはGCompareFuncが要求 されます。多少不都合な点があり、 GHashTableは、関数記号が同じにも 関わらず、GSListGTreeと同方法で GCompareFuncを取り扱いません。 ここではGCompareFuncは、等号演算子 とし、その引数が等しい場合TRUEを 返すとします。その際、qsort() -型比較関数であってはいけません。 キー比較関数は、ハッシュの衝突が同ハッシュスロットで複数のペアに 生じた場合に、適切なキー-値のペアを見つけるために使われます。 GHashTableを作成し破棄するには、 に示されている作成関数、 または消去関数を使います。glibは、ハッシュテーブルに蓄えられたデータを どのように破棄するのか知る方法が全くないと記憶しておいて下さい。 つまり、テーブル自体を破棄するにすぎません。
<structname role="C">GHashTable</structname> #include <glib.h> GHashTable* g_hash_table_new GHashFunc hash_funcGCompareFunc key_compare_func void g_hash_table_destroy GHashTable* hash_table
最も一般的なキー、整数、ポインタ、そして文字列に対しては、すぐに使用 できるハッシュや比較関数が提供されています。 これらは、に示されています。 整数用の関数は、gintへのポインタも 含んでいます。gint自体より 優れているかもしれません。ハッシュ関数の引数として、 g_hash_table_new()NULLを渡すとデフォルトでは、 g_direct_hash()が用いられます。 キー同等機能としてNULLを渡すと単純に ポインタ比較が使われます。 (g_direct_equal()と同等ですが、 関数のコールは行われません。 )
前書き ハッシュ/比較 #include <glib.h> guint g_int_hash gconstpointer v gint g_int_equal gconstpointer v1gconstpointer v2 guint g_direct_hash gconstpointer v gint g_direct_equal gconstpointer v1gconstpointer v2 guint g_str_hash gconstpointer v gint g_str_equal gconstpointer v1gconstpointer v2
ハッシュの処理は単純です。 ルーチンがに まとめられています。挿入は、キーまたは値をコピーしているわけでは ありません。これらは、まさに提供していると いったようにテーブルに入れられ、同キーを使用している既存の キー-値のペアは上書きされます。(ここでの"同キー"は、ハッシュや 同等の機能で定義されます。覚えておいて下さい。) もしこれが問題だとしたら、挿入の前に調べて、 取り除かなければなりません。特に注意しなければならないのは動的にキー 及び値を割り当てている場合です。 g_hash_table_lookup()は、発見した値をキーと 連結し、値がない場合はNULL を返します。ときには、そうならないこともあります。例えば、 NULLが、 それ自体有効値ということもあります。 キーとして文字列を使用している場合、特に動的に割り当てられた文字列の 場合、キーがテーブル内にあると知っている程度では十分ではない かもしれません。ハッシュテーブルが、 キー"foo"を表示するのに使っている 正確なgchar*を検索する必要性が考え られます。2つ目のlookup関数は、このような場合に提供されています。 g_hash_table_lookup_extended()は、成功すると TRUEを返します。そして、 TRUEを返すと、与えられた場所で 発見したキーと値を格納します。
<structname role="C">GHashTable</structname>の処理 #include <glib.h> void g_hash_table_insert GHashTable* hash_tablegpointer keygpointer value void g_hash_table_remove GHashTable * hash_tablegconstpointer key gpointer g_hash_table_lookup GHashTable * hash_tablegconstpointer key gboolean g_hash_table_lookup_extended GHashTable* hash_tablegconstpointer lookup_keygpointer* orig_keygpointer* value
GHashTable は、内部配列を確保します。 その大きさは、プライムです。更に、テーブルに格納されたキー-値の数を 記憶します。有効なスロットに対するペアの平均的数が、 0.3 (またはそれくらい)以下になると、配列はより小さくなります。 3以上になると、配列は衝突を減らすためより大きくなります。 大きさの変更は、挿入及びテーブルからペアを取り除くと 自動的に行われます。これによってハッシュテーブルのメモリ使用が 最適であるのは確実です。残念ながら、挿入または除去の数を大きくし、 何度もハッシュテーブルを再構築するのは効率がよくありません。 ハッシュテーブルが凍結可能であれば、 問題は解決します。大きさの変更が一時的に制御されるということです。 項目の追加や削除を行うと、単純にテーブルを解凍 するということです。結果としては、最適のサイズ計算になります。 (しかしながら注意が必要です。凍結したテーブルは、 大量のデータを加えると多数のハッシュ衝突に終わることがあります。 lookupを行う前解凍に従って、これを回復しなければなりません。) 関数は、に示されています。
<structname role="C">GHashTable</structname>の凍結と解凍 #include <glib.h> void g_hash_table_freeze GHashTable* hash_table void g_hash_table_thaw GHashTable* hash_table
その他の特徴 本書では、glibの全特色を取り扱う程の余地がありません。 "そこに本当に必要な機能は..."と考え悩んでいる と思ったら、glibをあたってみるといいでしょう。— http://www.gtk.org/glib.hやglibドキュメントは、 優れています。 これは、まだ触れていない機能の簡単なリストです。 FLOAT_MAX等。 多くの数値型と同等です。 バイト順変換。 g_memmove() は、memmove() より移植性があります。 G_DIR_SEPARATORは、 Windows/UNIX の相違を処理します。 G_VA_COPYは、移植式方法で va_listをコピーします。 移植式方法でコンパイラ拡張の使用を可能にする多数のマクロ。 (特にgcc拡張。) g_htonl()の移植とその他の ホスト-ネットワーク変換。 GCache 通常のキャッシュ機能。 "コールバックメンテナンス" ルーチン—コールバックの登録 及び登録解除。 g_log()機能は、可変なログレベルやプラグ可能な 出力ルーチンを使って、警告、メッセージ等を出力可能にします。 GMemChunk機能は、 g_malloc()と比較した効率面を考え、 小さな数々のメモリの大きなプールを割り当てられるようにします。 例えば、GListでの実装に 用いられます。 タイマー機能。 便利性/移植性ルーチン。ユーザのホームディレクトリを確保し、 /tmpディレクトリの名称を得たり、 それに類似することを行います。 ファイル名操作。g_basename()g_path_is_absolute()があります。 ビットフィールド操作。 強化文字列及び配列クラス。ポインタ及びバイト配列。 GQuark—2通りの文字列から 整数識別子へのマッピング。 データを文字列または任意のポインタで連結するルーチン。 レキシカル走査。 タブ完了。 カレンダ的/日時的-計算機能。 一般的イベントループの大要。GTK+イベントループ実装に使用されます。 移植可能なスレッドの大要。 まだ、glibに存在しない通常役立つルーチンを必要とする際は、glibの形式で それを書き、ライブラリに貢献していただけたらと思います。設計、デバッグ、 そしてメンテナンスでフリーの恩恵を得、他のプログラマは、今度はあなたが 作成された機能から利点を得ます。これを読んでいる間にも、必要とする機能が すでにglibの最新バージョンで存在する可能性もあるのです。 日本語翻訳:レーザーファイブ(info@laser5.co.jp)