Нередко в процессе разработки требуется вывести некоторую отладочную информацию, которая покажет нам состояние программы. Для этого, конечно, мы можем использовать стандартные функции для вывода на консоль. Однако можно сделать вывод более структурированным, оформив его в виде макроса. Например:
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) fprintf(stderr, "DEBUG: %s:%d:%s(): " fmt "\n", \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...) ((void)0)
#endif
Разберем этот макрос. Сначала мы проверяем, установлена ли константа DEBUG.
#ifdef DEBUG
То есть мы можем глобально включить вывод отладочной информации, определеив эту константу. А если нам не надо ничего выводить, то соответственно и данную константу можно не определять.
Затем идет собственно определение макроса - макроса DEBUG_PRINT. Данный макрос принимает как минимум один параметр - fmt:
DEBUG_PRINT(fmt, ...)
fmt - произвольный идентификатор, через который мы будем передавать строку форматирования для вывода информации на консоль. Затем идет многоточие ..., которое обозначает, что макрос будет принимать произвольное количество параметров (один, два, три или вообще ноль параметров). И эти параметры будут передаваться в строку форматирования для вывода на консоль.
Сам макрос разворачивается в следующий код:
fprintf(stderr, "DEBUG: %s:%d:%s(): " fmt "\n", \
__FILE__, __LINE__, __func__, __VA_ARGS__)
То есть для вывода будет применяться встроенная функция fprintf() (функция форматированного вывода). Первый параметр этой функции представляет поток, в который будет происходить вывод. В данном случае потоком вывода служит встроенная глобальная переменная stderr, которая обычно предназначена для вывода ошибок.
Второй параметр функции fprintf() представляет строку форматирования:
"DEBUG: %s:%d:%s(): " fmt "\n"
Сама строка форматирования состоит из трех подстрок. В языке C для конкатенации строк данные строки просто располагаются рядом. Первая подстрока содержит три параметра для вывода "%s:%d:%s()". Вторая подстрока - fmt - это та строка форматирования, которая передается через первый параметр макроса DEBUG_PRINT. И третья подстрока - "\n" - для переноса строки.
Для переноса определения макроса применяется слеш \ и далее идет собственно те параметры, которые подставляются в строки форматирования. В данном случае на место параметров "DEBUG: %s:%d:%s():" подставляются значения из встроенных констант
__FILE__(название файла), __LINE__(номер строки), __func__(название функции).
Все произвольные значения, которые передаются в макрос на место многоточия, можно получить через также встроенный идентификатор __VA_ARGS__ - это те значения, которые будут передаваться в строку fmt.
Данное определение макроса будет выполняться, если определена константа DEBUG. А на случай, если она не определена, устанавливаем заглушку:
#define DEBUG_PRINT(fmt, ...) ((void)0)
Данная заглушка фактически ничего не выполняет.
Применение макроса:
#include <stdio.h>
#define DEBUG // подключаем отладочное логгирование
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) fprintf(stderr, "DEBUG: %s:%d:%s(): " fmt "\n", \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...) ((void)0)
#endif
int sum(int a, int b){
DEBUG_PRINT("Parameters: %d %d", a, b);
return a + b;
}
int main(){
int x = 2;
int y = 4;
printf("x + y = %d\n", sum(x, y));
return 0;
}
Здесь определена константа DEBUG, соответственно будет выполняться логгирование. Если файлов много, естественно можно вынести определение константы в какой-нибудь общий заголовочный файл. Для тестирования используется функция sum, в которой логгируются параметры:
int sum(int a, int b){
DEBUG_PRINT("Parameters: %d %d", a, b);
return a + b;
}
Таким образом, строка "Parameters: %d %d" будет передаваться в макрос через параметр fmt, а переменные a и b - это произвольные параметры макроса, которые мы получаем через __VA_ARGS__.
И в данном случае консольный вывод может выглядеть следующим образом:
DEBUG: test.c:15:sum(): Parameters: 2 4 x + y = 6
Выше был представлен один из распространенных макрос отладки, который выводит отлажочную информацию на консоль. Но в целом макросы, предназначенные для отладки, гораздо шире и в зависимости от задач могут иметь различные формы. Например, возьмем следующюю программу:
#include <stdio.h>
#define YYDEBUG
#ifdef YYDEBUG
#define DEBUG(expr) { expr;}
#else
#define DEBUG(expr)
#endif
int main (void)
{
DEBUG(puts("Hello 1"));
DEBUG(puts("Hello 2"));
return 0;
}
Здесь, если определен макрос YYDEBUG, значит, приложение находится в режиме отладки, и также значит, что далее определяется макрос DEBUG:
#ifdef YYDEBUG
#define DEBUG(expr) { expr;}
Этот макрос принимает выражение, которое выполняется в блоке кода.
Если же макрос YYDEBUG не определен, то макрос DEBUG также определяется, только передаваемое в него выражение не выполняется:
#else #define DEBUG(expr) #endif
Возьмем применение макроса в функции main:
DEBUG(puts("Hello 1"));
Если макрос YYDEBUG определен, то фактически выполняется код {puts("Hello 1");}. Если же YYDEBUG не определен, то выражение DEBUG(puts("Hello 1")); заменяется пустотой, и ничего не выполняется.