Если вы используете компилятор C99 или новее
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Предполагается, что вы используете C99 (нотация списка переменных аргументов не поддерживается в более ранних версиях). Идиома do { ... } while (0)
гарантирует, что код действует как оператор (вызов функции). Безусловное использование кода гарантирует, что компилятор всегда проверяет правильность кода отладки, но оптимизатор удалит код, когда DEBUG равен 0.
Если вы хотите работать с #ifdef DEBUG, измените условие проверки:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
А затем используйте DEBUG_TEST, где я использовал DEBUG.
Если вы настаиваете на строковом литерале для строки формата (в любом случае, возможно, это хорошая идея), вы также можете ввести в вывод такие вещи, как __FILE__
, __LINE__
и __func__
, что может улучшить диагностику:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Это полагается на конкатенацию строк для создания строки формата большего размера, чем пишет программист.
Если вы используете компилятор C89
Если вы застряли на C89 и у вас нет полезного расширения компилятора, то нет особенно чистого способа справиться с этим. Я использовал следующую технику:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
А затем в коде напишите:
TRACE(("message %d\n", var));
Двойные круглые скобки имеют решающее значение, и именно поэтому у вас есть забавные обозначения в расширении макросов. Как и раньше, компилятор всегда проверяет код на синтаксическую достоверность (что хорошо), но оптимизатор вызывает функцию печати только в том случае, если макрос DEBUG не равен нулю.
В этом примере требуется вспомогательная функция dbg_printf () для обработки таких вещей, как 'stderr'. Для этого нужно знать, как писать функции varargs, но это несложно:
#include <stdarg.h>
#include <stdio.h>
void dbg_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
Вы также можете использовать эту технику в C99, но метод __VA_ARGS__
более аккуратный, потому что он использует обычную нотацию функций, а не взлом двойных круглых скобок.
Почему так важно, чтобы компилятор всегда видел отладочный код?
[Перефразирование комментариев к другому ответу]
Одна центральная идея, лежащая в основе описанных выше реализаций C99 и C89, заключается в том, что собственно компилятор всегда видит отладочные операторы, подобные printf. Это важно для долгосрочного кода, который будет длиться десять или два десятилетия.
Предположим, что часть кода в течение нескольких лет в основном бездействовала (стабильна), но теперь ее нужно изменить. Вы повторно включаете отладочную трассировку, но вам неприятно отлаживать отладочный (трассирующий) код, потому что он относится к переменным, которые были переименованы или перепечатаны за годы стабильного обслуживания. Если компилятор (пост-препроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит инструкцию print, он не может защитить вас от вашей собственной невнимательности (или небрежности ваших коллег или сотрудников). См. «Практика программирования» Кернигана и Пайка, особенно главу 8. (см. также Википедию на TPOP).
Это опыт «был там, сделал то». Я использовал по существу технику, описанную в других ответах, где не отладочная сборка не видит printf-подобных операторов в течение нескольких лет (более десяти лет). Но я натолкнулся на совет в TPOP (см. Мой предыдущий комментарий), а затем через несколько лет включил некоторый код отладки и столкнулся с проблемами изменения контекста, нарушающего отладку. Несколько раз постоянная проверка печати спасала меня от последующих проблем.
Я использую NDEBUG только для управления утверждениями и отдельный макрос (обычно DEBUG), чтобы контролировать, встроена ли в программу трассировка отладки. Даже когда трассировка отладки встроена, я часто не хочу, чтобы выходные данные отладки отображались безоговорочно, поэтому у меня есть механизм, позволяющий контролировать, отображаются ли выходные данные (уровни отладки, и вместо прямого вызова fprintf()
я вызываю функцию печати отладки, которая только условно печатает, чтобы та же самая сборка кода могла печатать или не печатать в зависимости от параметров программы). У меня также есть версия кода для «нескольких подсистем» для более крупных программ, так что я могу иметь разные разделы программы, производящие разное количество трассировки - под контролем времени выполнения.
Я выступаю за то, чтобы для всех сборок компилятор видел диагностические операторы; однако компилятор не будет генерировать код для операторов трассировки отладки, если отладка не включена. По сути, это означает, что весь ваш код проверяется компилятором каждый раз, когда вы компилируете - будь то выпуск или отладка. Это хорошая вещь!
debug.h - версия 1.2 (1990-05-01)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 1.2 $
@(#)Last changed: $Date: 1990/05/01 12:55:39 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
*/
#ifndef DEBUG_H
#define DEBUG_H
/* -- Macro Definitions */
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x)
#endif /* DEBUG */
/* -- Declarations */
#ifdef DEBUG
extern int debug;
#endif
#endif /* DEBUG_H */
debug.h - версия 3.6 (11.02.2008)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 3.6 $
@(#)Last changed: $Date: 2008/02/11 06:46:37 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product: :PRODUCT:
*/
#ifndef DEBUG_H
#define DEBUG_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
/*
** Usage: TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x) do { if (0) db_print x; } while (0)
#endif /* DEBUG */
#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */
#include <stdio.h>
extern int db_getdebug(void);
extern int db_newindent(void);
extern int db_oldindent(void);
extern int db_setdebug(int level);
extern int db_setindent(int i);
extern void db_print(int level, const char *fmt,...);
extern void db_setfilename(const char *fn);
extern void db_setfileptr(FILE *fp);
extern FILE *db_getfileptr(void);
/* Semi-private function */
extern const char *db_indent(void);
/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/
/*
** Usage: MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x) db_mdprint x
#else
#define MDTRACE(x) do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */
extern int db_mdgetdebug(int subsys);
extern int db_mdparsearg(char *arg);
extern int db_mdsetdebug(int subsys, int level);
extern void db_mdprint(int subsys, int level, const char *fmt,...);
extern void db_mdsubsysnames(char const * const *names);
#endif /* DEBUG_H */
Вариант с одним аргументом для C99 или новее
Кайл Брандт спросил:
Как бы то ни было, чтобы debug_print
работал, даже если нет аргументов? Например:
debug_print("Foo");
Есть один простой старомодный прием:
debug_print("%s\n", "Foo");
Решение только для GCC, показанное ниже, также поддерживает это.
Однако вы можете сделать это с помощью простой системы C99, используя:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
По сравнению с первой версией вы теряете ограниченную проверку, которая требует аргумента fmt, что означает, что кто-то может попытаться вызвать debug_print () без аргументов (но конечная запятая в списке аргументов на fprintf()
не сможет скомпилировать ). Спорный вопрос, является ли потеря проверки вообще проблемой.
Специфичная для GCC техника для одного аргумента
Некоторые компиляторы могут предлагать расширения для других способов обработки списков аргументов переменной длины в макросах. В частности, как впервые было отмечено в комментариях Хьюго Иделера, GCC позволяет опускать запятую, которая обычно появляется после последний «фиксированный» аргумент макроса. Он также позволяет использовать ##__VA_ARGS__
в тексте замены макроса, который удаляет запятая, предшествующая обозначению, если, но только если предыдущий токен является запятой:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Это решение сохраняет преимущество требования аргумента формата при принятии необязательных аргументов после формата.
Этот метод также поддерживается Clang для совместимости с GCC.
Почему цикл do-while?
Для чего здесь do while
?
Вы хотите иметь возможность использовать макрос, чтобы он выглядел как вызов функции, что означает, что после него будет стоять точка с запятой. Следовательно, вы должны упаковать тело макроса в соответствии с требованиями. Если вы используете оператор if
без окружающего do { ... } while (0)
, вы получите:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Теперь предположим, что вы пишете:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
К сожалению, этот отступ не отражает фактическое управление потоком, потому что препроцессор создает код, эквивалентный этому (с отступом и фигурными скобками, добавленными, чтобы подчеркнуть фактическое значение):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Следующая попытка макроса может быть такой:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
И тот же фрагмент кода теперь производит:
if (x > y)
if (DEBUG)
{
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
}
; // Null statement from semi-colon after macro
else
do_something_useful(x, y);
И else
теперь является синтаксической ошибкой. Цикл do { ... } while(0)
позволяет избежать обеих этих проблем.
Есть еще один способ написать макрос, который может сработать:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
Это оставляет фрагмент программы, показанный как действительный. Приведение (void)
предотвращает его использование в контекстах, где требуется значение, но его можно использовать как левый операнд оператора запятой, где версия do { ... } while (0)
не может. Если вы думаете, что сможете встраивать отладочный код в такие выражения, вы можете предпочесть это. Если вы предпочитаете, чтобы отладочная печать действовала как полный оператор, тогда версия do { ... } while (0)
лучше. Обратите внимание: если в теле макроса есть точки с запятой (грубо говоря), то вы можете использовать только нотацию do { ... } while(0)
. Это всегда работает; механизм выражения выражения может быть более трудным в применении. Вы также можете получать предупреждения от компилятора с формой выражения, которую вы предпочитаете избегать; это будет зависеть от компилятора и используемых вами флагов.
TPOP ранее был на http://plan9.bell-labs.com/cm/cs/tpop и http://cm.bell-labs.com/cm/cs/tpop, но оба сейчас (10.08.2015) не работают.
Код в GitHub
Если вам интересно, вы можете посмотреть этот код на GitHub в моем репозитории SOQ (Stack Overflow Questions). как файлы debug.c
, debug.h
и mddebug.c
в подкаталоге src / libsoq .
person
Jonathan Leffler
schedule
29.10.2009