Skip to content

emka.web.id

Menu
  • Home
  • Indeks Artikel
  • Tutorial
  • Tentang Kami
Menu

Glib Debugging Macros

Posted on April 08, 2012 by Syauqi Wiryahasana
glib has a nice set of macros you can use to enforce invariants and preconditions in your code. GTK+ uses these liberally—one of the reasons it’s so stable and easy to use. They all disappear when you define G_DISABLE_CHECKS or G_DISABLE_ASSERT, so there’s no performance penalty in production code. Using these liberally is a very, very good idea. You’ll find bugs much faster if you do. You can even add assertions and checks whenever you find a bug to be sure the bug doesn’t reappear in future versions—this complements a regression suite. Checks are especially useful when the code you’re writing will be used as a black box by other programmers; users will immediately know when and how they’ve misused your code. Of course you should be very careful to ensure your code isn’t subtly dependent on debug-only statements to function correctly. Statements that will disappear in pro- duction code should never have side effects. [code lang="cpp"] #include <glib.h> g_return_if_fail(condition); g_return_val_if_fail(condition, retval); [/code] Figure 2-3. Precondition Checks Figure 2-3 shows glib’s precondition checks. g_return_if_fail() prints a warning and immediately returns from the current function if condition is FALSE. g_return_val_if_fail() is similar but allows you to return some retval. These macros are incredibly useful—if you use them liberally, especially in combination with GTK+’s runtime type check- ing, you’ll halve the time you spend looking for bad pointers and type errors. Using these functions is simple; here’s an example from the glib hash table imple- mentation: [code lang="cpp"] 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); } [/code] Without the checks, passing NULL as a parameter to this function would result in a mysterious segmentation fault. The person using the library would have to figure out where the error occurred with a debugger, and maybe even dig in to the glib code to see what was wrong. With the checks, they’ll get a nice error message telling them that NULL arguments are not allowed. [code lang="cpp"] #include <glib.h> g_assert(condition); g_assert_not_reached(void); [/code] Figure 2-4. Assertions glib also has more traditional assertion macros, shown in Figure 2-4. g_assert() is basically identical to assert(), but responds to G_DISABLE_ASSERT and behaves consistently across all platforms. g_assert_not_reached() is also provided; this is an assertion which always fails. Assertions call abort() to exit the program and (if your environment supports it) dump a core file for debugging purposes. Fatal assertions should be used to check internal consistency of a function or library, while g_return_if_fail() is intended to ensure sane values are passed to the pub- lic interfaces of a program module. That is, if an assertion fails, you typically look for a bug in the module containing the assertion; if a g_return_if_fail() check fails, you typically look for the bug in the code which invokes the module. This code from glib’s calendrical calculations module shows the difference: [code lang="cpp"] 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; } [/code] The precondition check at the beginning ensures the user passes in reasonable values for the day, month and year; the assertion at the end ensures that glib constructed a sane object, given sane values. g_assert_not_reached() should be used to mark "impossible" situations; a com- mon use is to detect switch statements that don’t handle all possible values of an enumeration: [code lang="cpp"] switch (val) { case FOO_ONE: break; case FOO_TWO: break; default: /* Invalid enumeration value */ g_assert_not_reached(); break; } [/code] All of the debugging macros print a warning using glib’s g_log() facility, which means the warning includes the name of the originating application or library, and you can optionally install a replacement warning-printing routine. For example, you might send all warnings to a dialog box or log file instead of printing them on the console. Source: Havoc Pennington. 1999. GTK Gnome Application Development. New York: New Riders Publishing
Seedbacklink

Recent Posts

TENTANG EMKA.WEB>ID

EMKA.WEB.ID adalah blog seputar teknologi informasi, edukasi dan ke-NU-an yang hadir sejak tahun 2011. Kontak: kontak@emka.web.id.

©2024 emka.web.id Proudly powered by wpStatically