2.1.3 Макросы для отладки

glib имеет хороший набор макросов, которые вы можете использовать для увеличения инвариантов и предусловий в вашем коде. Они исчезнут после того, как вы определите "G_DISABLE_CHECKS" или "G_DISABLE_ASSERT", поэтому уменьшения производительности в реальном коде не будет. Рекомендуется широко использовать это. Тогда вы сможете находить ошибки намного быстрее. Вы можете даже добавлять условия и проверки всякий раз, когда находите ошибку, чтобы быть уверенными, что ошибка не появится в будущих версиях -- это дополняет регрессивный набор. Проверки особенно полезны, когда код, который вы пишете будет использоваться как черный ящик другими программистами; пользователи будут сразу же знать когда и как они неправильно использовали ваш код.

Естественно, вы должны быть уверены, что ваш код функционирует правильно не только в режиме отладки. Операторы, которые присутствуют в финальном коде никогда не должны иметь побочных эффектов.

Список макросов 2..3: Проверки предусловий
"#include "<glib.h>
g_return_if_fail(condition) g_return_val_if_fail(condition, retval)

Список макросов 2..3 показывает проверки предусловий glib. "g_return_if_fail()" выводит предупреждение и выходит из текущей функции если условие condition ложно. "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 недопустимы.

Список макросов 2..4: Условия
"#include "<glib.h>
g_assert(condition) g_assert_not_reached()

В glib также есть традиционные макросы условий, показанные в списке макросов 2..4. "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; }

Все отладочные макросы печатают предупреждения, используя "g_log()" из glib, что означает, что предупреждение включает имя порождающей его программы или библиотеки, и вы можете дополнительно установить замену для процедуры печати предупреждений. Например, вы можете посылать все предупреждения в диалоговое окно или лог-файл, вместо печати их на консоль.


Linux Land
2000-09-15