Если вы используете компилятор C99 или новее
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Предполагается, что вы используете C99 (нотация списка аргументов переменной не поддерживается в более ранних версиях). Идиома do { ... } while (0)
гарантирует, что код действует как оператор (вызов функции). Безусловное использование кода гарантирует, что компилятор всегда проверяет правильность вашего кода отладки & mdash; но оптимизатор удалит код, когда 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));
Двойные скобки имеют решающее значение & mdash; и вот почему у вас есть забавные обозначения в расширении макроса. Как и прежде, компилятор всегда проверяет код на синтаксическую достоверность (что хорошо), но оптимизатор вызывает функцию печати только в том случае, если макрос DEBUG оценивается как ненулевой.
Для этого требуется функция поддержки & mdash; dbg_printf () в примере & mdash; обрабатывать такие вещи, как «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. Это важно для долгосрочного кода & mdash; код, который будет длиться десятилетие или два.
Предположим, что часть кода была в основном неактивной (стабильной) в течение ряда лет, но теперь ее необходимо изменить. Вы снова включаете трассировку отладки, но разочаровывает необходимость отлаживать код отладки (трассировки), поскольку он ссылается на переменные, которые были переименованы или перепечатаны в годы стабильного обслуживания. Если компилятор (постпроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит оператор печати, он не может защитить вас от вашей собственной небрежности (или небрежности ваших коллег или сотрудников). См. « Практика программирования » Кернигана и Пайка, особенно главу 8 (см. Также Википедию по TPOP ).
Это «был там, сделал это», опыт & ndash; Я в основном использовал технику, описанную в других ответах, где не отладочная сборка не видит подобные 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)
предотвращает его использование в контекстах, где требуется значение & mdash; но его можно использовать в качестве левого операнда оператора запятой, где версия 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
Переполнение Вопросы) хранилище в виде файлов debug.c
, debug.h
и mddebug.c
в
ЦСИ / libsoq
подкаталог.