Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Gtk+/Gnome Application Development
Prev Home Next

Debugging Macros

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 production code should never have side effects.

#include <glib.h>

g_return_if_fail(condition);

g_return_val_if_fail(condition, retval);

Figure 3. Precondition Checks

Figure 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 checking, 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 implementation:


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);
}

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.

#include <glib.h>

g_assert(condition);

g_assert_not_reached(void);

Figure 4. Assertions

glib also has more traditional assertion macros, shown in Figure 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 public 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:


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;
}

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 common use is to detect switch statements that don't handle all possible values of an enumeration:


  switch (val) 
    {
      case FOO_ONE:
        break;
      case FOO_TWO:
        break;
      default:
        /* Invalid enumeration value */
        g_assert_not_reached();
        break;
    }

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.

Gtk+/Gnome Application Development
Prev Home Next

 
 
  Published under free license. Design by Interspire