Если вы используете компилятор 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. Это важно для долгосрочного кода - кода, который будет длиться десять или два года.
Предположим, что часть кода была в основном неактивной (стабильной) в течение ряда лет, но теперь ее необходимо изменить. Вы снова активируете трассировку отладки, но разочаровывает необходимость отладки кода отладки (трассировки), поскольку он ссылается на переменные, которые были переименованы или перепечатаны в годы стабильного обслуживания. Если компилятор (постпроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит оператор печати, он не может защитить вас от вашей собственной небрежности (или небрежности ваших коллег или сотрудников). См. « Практику программирования » Кернигана и Пайка, особенно главу 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 (2008-02-11)
/*
@(#)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, но оба сейчас (2015-08-10) сломана.
Код в GitHub
Если вам интересно, вы можете посмотреть на этот код в GitHub в моих SOQ (Stack Overflow) Вопросы хранилища в виде файлов debug.c
, debug.h
и mddebug.c
в
SRC / libsoq
подкаталоге.