#define макрос для отладочной печати на C?

Попытка создать макрос, который можно использовать для печати отладочных сообщений, когда определена DEBUG, например, следующий псевдокод:

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

Как это достигается с помощью макроса?


person jfarrell    schedule 29.10.2009    source источник
comment
Будет ли компилятор (gcc) оптимизировать такие операторы, как if (DEBUG) {...} out, если в производственном коде макрос DEBUG установлен в 0? Я понимаю, что есть веские причины оставить операторы отладки видимыми для компилятора, но плохое предчувствие остается. -Пат   -  person Pat    schedule 02.02.2010


Ответы (14)


Если вы используете компилятор 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
comment
В любом случае, чтобы сделать это так, чтобы debug_print все еще работал, даже если нет аргументов? IE debug_print (Foo); ? - person Kyle Brandt; 30.10.2009
comment
Всегда есть старомодный прием: debug_print (% s \ n, Foo); : D Постараюсь разобраться, есть ли альтернатива. Решение только для GCC обеспечивает поддержку этого. - person Jonathan Leffler; 30.10.2009
comment
Какая цель здесь do while? - person cYrus; 14.01.2012
comment
@cYrus Как сказал Джонатан в своем ответе, цель do {} while (0) - заставить компиляторы C99 проверить код отладки. Без него компилятор мог бы увидеть, что условие в if (DEBUG) ложно, и пропустить код. Таким образом снижается вероятность ошибок в отладочном коде. - person Thomas Dignan; 20.02.2012
comment
Спустя годы, и этот ответ по-прежнему остается самым полезным из всех интернета о том, как использовать псевдоним printk! vfprintf не работает в пространстве ядра, так как stdio недоступен. Спасибо! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0) - person Kevin; 19.07.2013
comment
В вашем примере с ключевыми словами __FILE__, __LINE__, __func__, __VA_ARGS__ он не будет компилироваться, если у вас нет параметров printf, т.е. если вы просто вызываете debug_print("Some msg\n");. Вы можете исправить это, используя fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ не позволяет передавать параметры функции. - person mc_electron; 11.06.2014
comment
@mc_electron: я считаю, что ваша проблема описана в разделах «Вариант C99 с одним аргументом» и «Техника, специфичная для GCC» в основном ответе. Если вы согласны, я предлагаю удалить ваш комментарий и пометить его как устаревший. Если нет, то лучше обсудить это, возможно, по электронной почте (см. Мой профиль). - person Jonathan Leffler; 12.06.2014
comment
не могли бы вы объяснить больше, но его можно использовать как левый операнд оператора запятой, где версия do {...} while (0) не может. в последнем абзаце вашего ответа? что вы имеете в виду под левым операндом оператора запятой? - person bysreg; 05.06.2016
comment
@bysreg: оператор запятой имеет два операнда, например: var1 = expr1, function(arg1, arg2). Левый операнд здесь var1 = expr1; правый операнд function(arg1, arg2). При выполнении оценивается левый операнд (для его побочных эффектов - присваивание в этом примере), а затем значение отбрасывается, а правый операнд оценивается, и его значение является значением выражения оператора запятой. С #define debug_print(...) ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0)) вы можете написать: if (var1 == var2) debug_printf("var1 == var2\n"), var2++;, если хотите (но почему?). - person Jonathan Leffler; 06.06.2016
comment
По сравнению с первой версией вы теряете ограниченную проверку, которая требует аргумента «fmt». Вы можете уточнить, какая проверка потеряна? Спасибо. - person LogicTom; 21.06.2016
comment
@LogicTom: разница между #define debug_print(fmt, ...) и #define debug_print(...). Для первого из них требуется по крайней мере один аргумент, строка формата (fmt) и ноль или более других аргументов; для второго требуется всего ноль или более аргументов. Если вы используете debug_print() с первым, вы получите ошибку от препроцессора о неправильном использовании макроса, а второй - нет. Тем не менее, вы все равно получаете ошибки компиляции, потому что текст замены недействителен C. Итак, на самом деле это не так много разницы - отсюда и использование термина «ограниченная проверка». - person Jonathan Leffler; 21.06.2016
comment
Несколько более элегантный и короткий способ - использовать завершающий else вместо цикла do while: #define debug_print(fmt, ...) if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); else - person Guillermo; 02.10.2016
comment
@Guillermo: Красота и элегантность в глазах смотрящего. Я предпочитаю то, что использую, а не то, что вы предлагаете, так как это более элегантно для моих глаз. - person Jonathan Leffler; 02.10.2016
comment
@JonathanLeffler Не __LINE__ сообщит о неправильной строке, если она встроена так? Разве его нельзя передать как аргумент самому макросу? - person Zingam; 19.09.2017
comment
@Zingam: Я не уверен, почему вы думаете, что с __LINE__ поступят неправильно. Это значение номера строки, в которой вызывается макрос, а не там, где он определен. Вы это тестировали? В отсутствие конкретного примера того, как это неправильно, ответ будет отрицательным: __LINE__ не будет сообщать неправильное значение для большинства определений «неправильно». - person Jonathan Leffler; 19.09.2017
comment
@JonathanLeffler Я полагаю, что у меня была такая проблема в прошлом. Я думаю, что репоированная строка не будет такой же, как строка, в которой был вызван макрос. Я протестирую ваш пример, когда дойду до компилятора. - person Zingam; 19.09.2017
comment
@Zingam: Если вы обнаружите воспроизводимую проблему, дайте мне знать. Скорее всего, это будет слишком для комментария - пришлите мне об этом по электронной почте (см. Мой профиль). Пожалуйста, включите информацию о платформе - особенно о том, какой компилятор и т.д. ограниченный интерес, но я бы добавил об этом замечание. Если это текущая версия достаточно распространенного компилятора, мне это очень интересно. Если проблем нет, удалите свои комментарии и отметьте мои. - person Jonathan Leffler; 19.09.2017
comment
@JonathanLeffler предположим, что вы хотите вызвать #define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ __LINE__, __func__, __VA_ARGS__); } while (0) следующим образом: char *fmt = "anything : %s\n"; debug_print(fmt, "blah"); gcc будет компилироваться с ошибкой: error: expected ‘)’ before ‘fmt’. Если я назову его как debug_print("anything : %s\n", "blah");, он успешно компилируется. Каково объяснение этой ошибки компиляции и какое-либо представление о том, как ее добиться с помощью переменной в качестве аргумента для fmt? - person HeinrichStack; 14.11.2017
comment
@HeinrichStack: ошибку легко объяснить. Макрос полагается на конкатенацию строковых литералов, чтобы «вставить» некоторый форматный материал перед тем, что вы указываете в качестве аргумента макроса. Когда вы используете переменную для формата, вы нарушаете предварительные условия. Как это исправить? Вместо прямого вызова fprintf() вызовите функцию, которая сама обрабатывает печать. Не нужно добавлять постоянную строку в ваш формат. Вероятно, он будет использовать flockfile() и funlockfile() для обеспечения безопасности потоков (в системе POSIX). - person Jonathan Leffler; 14.11.2017
comment
@JonathanLeffler Боюсь, мне нужно больше ваших объяснений, что вы имели в виду с handles the printing itself, и почему вы упомянули flockfile()? - person HeinrichStack; 15.11.2017
comment
Насколько я понимаю, вы устанавливаете уровень отладки для каждой единицы перевода. Почему бы просто не установить уровень для каждого приложения при запуске, который считывается из некоторой конфигурации? - person St.Antario; 10.04.2019
comment
Показанный выше вариант, @ St.Antario, использует один активный уровень отладки для всего приложения, и я обычно использую параметры командной строки, чтобы разрешить установку уровня отладки при запуске программы. У меня также есть вариант, который распознает несколько разных подсистем, каждой из которых дается имя и собственный уровень отладки, так что я могу использовать -D input=4,macros=9,rules=2, чтобы установить уровень отладки системы ввода на 4, систему макросов на 9 (претерпевает интенсивные проверка) и систему правил до 2. Есть бесконечные вариации на тему; используйте то, что вам подходит. - person Jonathan Leffler; 10.04.2019
comment
Как я могу сделать что-то подобное для потоковой печати? Я хочу управлять печатью, используя такую ​​логику, как сравнение серьезности сообщения с выбранным уровнем детализации, и использовать ее в пользовательском коде следующим образом: my_cerr (50) ‹< Имя файла: ‹< fname ‹< '\ n'; Как бы выглядел мой макрос my_cerr (x)? - person All the Rage; 20.11.2019
comment
@AlltheRage - честно говоря, я понятия не имею, как писать макросы, показанные для использования на C ++. C ++ пытается избежать препроцессора C. Списки аргументов переменной длины, переменных типов, поддерживаемые printf() и т. Д., Не работают с пользовательскими типами. Вам следует проконсультироваться с другими организациями о том, как написать такой код для потоков ввода-вывода C ++. Одна из возможностей состоит в том, чтобы потребовать от пользователя вызвать TRACE("Filename is: " << fname << '\n') (чтобы пользователь вводил RHS, а затем макрос ставил перед ним какой-нибудь подходящий префикс, например cerr << . Это, вероятно, не так просто. - person Jonathan Leffler; 20.11.2019
comment
Мне очень нравится ваш вариативный макрос, содержащий __FILE__, __LINE__, & __func__! Супер полезно! - person Gabriel Staples; 14.12.2019
comment
Обновление: просто убедитесь, что используете ##__VA_ARGS__, как здесь прокомментировал @mc_electron и позже объяснил в этом ответе, чтобы разрешить отладочную печать со строкой формата, которая не требует аргументов. Если вместо этого вы используете только __VA_ARGS__, это ошибка компилятора. См. Документацию gcc здесь (gcc.gnu.org/onlinedocs/cpp/Variadic- Macros.html), где говорится: оператор вставки токена '##' имеет особое значение, когда помещается между запятой и аргументом переменной, если аргумент переменной не используется при использовании [макроса], то будет удалена запятая перед '##'. - person Gabriel Staples; 14.12.2019
comment
... Этого не произойдет, если вы передадите пустой аргумент, и этого не произойдет, если токен, предшествующий "##", не является чем-то другим, кроме запятой. - person Gabriel Staples; 14.12.2019
comment
Не забывайте, что есть и другие компиляторы, кроме GCC - использование специфичного для GCC расширения не поможет тем, кто не использует GCC (или компилятор, совместимый с расширением GCC). - person Jonathan Leffler; 14.12.2019
comment
как насчет использования ({}) для помещения части if (DEBUG) {code} в блок? тогда это if не будет иметь значения для оператора else за пределами - person Atreyagaurav; 23.04.2020
comment
Это не C. Я не использую эту функцию GCC. - person Jonathan Leffler; 23.04.2020

Я использую что-то вроде этого:

#ifdef DEBUG
 #define D if(1) 
#else
 #define D if(0) 
#endif

Тогда я просто использую D в качестве префикса:

D printf("x=%0.3f\n",x);

Компилятор видит отладочный код, проблем с запятыми нет, работает везде. Также он работает, когда printf недостаточно, например, когда вы должны выгрузить массив или вычислить какое-то диагностическое значение, которое является избыточным для самой программы.

РЕДАКТИРОВАТЬ: Хорошо, это может вызвать проблему, когда где-то рядом есть else, который может быть перехвачен этим введенным if. Это версия, которая проходит через это:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif
person mbq    schedule 18.09.2011
comment
Что касается for(;0;), это может вызвать проблему, если вы напишете что-то вроде D continue; или D break;. - person ACcreator; 10.09.2014
comment
Подловил; однако кажется очень маловероятным, что это может произойти случайно. - person mbq; 03.04.2015

Для переносимой (ISO C90) реализации вы можете использовать двойные круглые скобки, например:

#include <stdio.h>
#include <stdarg.h>

#ifndef NDEBUG
#  define debug_print(msg) stderr_printf msg
#else
#  define debug_print(msg) (void)0
#endif

void
stderr_printf(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
}

int
main(int argc, char *argv[])
{
  debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
  return 0;
}

или (хакерский, не рекомендую)

#include <stdio.h>

#define _ ,
#ifndef NDEBUG
#  define debug_print(msg) fprintf(stderr, msg)
#else
#  define debug_print(msg) (void)0
#endif

int
main(int argc, char *argv[])
{
  debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
  return 0;
}
person Marcin Koziuk    schedule 29.10.2009
comment
@LB: чтобы препроцессор "думал", есть только один аргумент, позволяя раскрыть _ на более позднем этапе. - person Marcin Koziuk; 30.10.2009

Вот версия, которую я использую:

#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif
person Christoph    schedule 29.10.2009

Я бы сделал что-то вроде

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

Я думаю, это чище.

person LB40    schedule 29.10.2009
comment
Мне не очень нравится идея использовать макрос внутри теста в качестве флага. Не могли бы вы объяснить, почему отладочную печать нужно всегда проверять? - person LB40; 29.10.2009
comment
@Jonathan: Если код всегда запускается только в режиме отладки, почему вас должно волновать, компилируется ли он в режиме без отладки? assert() из stdlib работает таким же образом, и я обычно просто повторно использую макрос NDEBUG для своего собственного кода отладки ... - person Christoph; 29.10.2009
comment
используя DEBUG в тесте, если кто-то выполняет неконтролируемую undef DEBUG, ваш код больше не компилируется. Правильно ? - person LB40; 29.10.2009
comment
Раздражает включение отладки, а затем необходимость отладки отладочного кода, потому что он относится к переменным, которые были переименованы или перепечатаны, и т. Д. Если компилятор (пост-препроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения будут не аннулировала диагностику. Если компилятор не видит инструкцию print, он не может защитить вас от вашей собственной невнимательности (или небрежности ваших коллег или сотрудников). См. «Практика программирования» Кернигана и Пайка - plan9.bell-labs.com / cm / cs / tpop. - person Jonathan Leffler; 29.10.2009
comment
@Jonathan: вот почему вы добавляете -DNDEBUG только для сборок выпуска (или их тестирования) - person Christoph; 29.10.2009
comment
Это опыт «был там, сделал то» - я, по сути, использовал технику, описанную в этом ответе, в течение нескольких лет (более десяти лет). Но я натолкнулся на совет в TPOP (см. Мой предыдущий комментарий), а затем через несколько лет включил некоторый код отладки и столкнулся с проблемами изменения контекста, нарушающего отладку. Несколько раз постоянная проверка печати спасала меня от последующих проблем. NB: Вы можете упростить текст замены макроса без отладки до '((void) 0)'. - person Jonathan Leffler; 29.10.2009
comment
@Christoph: ну, вроде ... Я использую NDEBUG только для управления утверждениями и отдельный макрос (обычно DEBUG) для управления трассировкой отладки. Я часто не хочу, чтобы выходные данные отладки отображались безоговорочно, поэтому у меня есть механизм для управления отображением выходных данных (уровни отладки, и вместо прямого вызова fprintf () я вызываю функцию печати отладки, которая печатает только условно, поэтому та же сборка код может печатать или не печатать в зависимости от параметров программы). Я выступаю за то, чтобы для всех сборок компилятор видел диагностические операторы; однако он не будет генерировать код, если не включена отладка. - person Jonathan Leffler; 29.10.2009
comment
@Jon, хорошие объяснения и хорошие ссылки. Только один вопрос, на чем вы основываете свои условные распечатки (каково условие внутри теста). Вы сопоставляете параметры программы с переменными? а затем использовать эти переменные или, точнее, определить DEBUG в соответствии с параметрами программы? - person LB40; 29.10.2009
comment
@LB: я на самом деле вызываю функцию печати безоговорочно, и она решает, печатать ли (см. Заголовки в моем ответе, который в настоящее время является принятым ответом). Для глобальной переменной debug был задан уровень отладки (ноль или отрицательный, без вывода; увеличение положительного числа дает больше результатов, в целом), а макрос TRACE () включал уровень отладки в качестве первого аргумента: TRACE ((3, Hi )); был оператором отладки уровня 3 - вызов db_print (3, Hi). Обычно у меня есть параметры командной строки для включения отладки: -d включает уровень 3, а -Dn включает уровень n. [...продолжение следует...] - person Jonathan Leffler; 29.10.2009
comment
@LB: ... Вы можете написать неотладочную версию макроса как 'do {if (0) print_function (...); } пока (0) '. У вас может быть отладочная версия как 'do {if (debug) printf_function (...); } while (0) 'и т. д. Есть много способов сделать это - иногда C больше похож на Perl, чем на Perl! (Девиз Perl - TMTOWTDI - Тим-Тоуди - есть больше, чем один способ сделать это.) - person Jonathan Leffler; 29.10.2009
comment
Небольшой момент: я бы использовал ((void)0) вместо пустого do-loop - person David R Tribble; 30.10.2009

Согласно http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html, перед __VA_ARGS__ должен стоять ##.

В противном случае макрос #define dbg_print(format, ...) printf(format, __VA_ARGS__) не будет компилировать следующий пример: dbg_print("hello world");.

person Chobits Tai    schedule 09.02.2012
comment
Добро пожаловать в Stack Overflow. Вы правы, что у GCC есть нестандартное расширение, на которое вы ссылаетесь. В принятом на данный момент ответе это действительно упоминается, включая именно указанный вами ссылочный URL. - person Jonathan Leffler; 28.10.2012

Вот что я использую:

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

У него есть приятное преимущество - правильно обрабатывать printf, даже без дополнительных аргументов. В случае, если DBG == 0, даже самый тупой компилятор не получает ничего, что можно было бы пережевывать, поэтому код не генерируется.

person 5tenzel    schedule 05.10.2014
comment
Лучше, чтобы компилятор всегда проверял отладочный код. - person Jonathan Leffler; 08.02.2015

Мой любимый из нижеприведенных - var_dump, который при назывании:

var_dump("%d", count);

производит такой вывод:

patch.c:150:main(): count = 0

Кредит @ "Джонатан Леффлер". Все довольны C89:

Код

#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf x; }} while (0)

/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)

#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
    __FILE__,  __LINE__, __func__); }} while (0)
person Tom Hale    schedule 06.09.2016

Итак, при использовании gcc мне нравится:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

Потому что это можно вставить в код.

Предположим, вы пытаетесь отладить

printf("%i\n", (1*2*3*4*5*6));

720

Затем вы можете изменить его на:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

И вы можете получить анализ того, какое выражение к чему было оценено.

Он защищен от проблемы двойной оценки, но отсутствие генсимов оставляет его открытым для конфликтов имен.

Однако это гнездо:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

Так что я думаю, что пока вы избегаете использования g2rE3 в качестве имени переменной, все будет в порядке.

Конечно, я нашел это (и родственные ему версии для строк, версии для уровней отладки и т. Д.) Бесценным.

person John Lawrence Aspden    schedule 11.10.2018

Я много лет размышлял, как это сделать, и наконец нашел решение. Однако я не знал, что здесь уже есть другие решения. Во-первых, в отличие от ответа Леффлера, я не вижу его аргументов в пользу того, что отладочные отпечатки всегда должны компилироваться. Я бы предпочел, чтобы в моем проекте не выполнялись тонны ненужного кода, когда он не нужен, в тех случаях, когда мне нужно протестировать, и они могут не быть оптимизированы.

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

Мое решение также предусматривает уровни детализации отладки; и если вы установите его на самый высокий уровень, все они компилируются. Если вы недавно использовали высокий уровень детализации отладки, тогда все они могли компилироваться. Окончательные обновления должны быть довольно простыми. Мне никогда не требовалось больше трех уровней, но Джонатан говорит, что использовал девять. Этот метод (как и метод Леффлера) можно расширить до любого количества уровней. Использование моего метода может быть проще; требуя всего двух операторов при использовании в вашем коде. Я, однако, тоже кодирую макрос CLOSE, хотя он ничего не делает. Может, если бы я отправлял в файл.

За счет затрат дополнительный этап тестирования их, чтобы убедиться, что они будут скомпилированы перед доставкой, заключается в том, что

  1. Вы должны доверять им, чтобы они были оптимизированы, что, по общему признанию, ДОЛЖНО произойти, если у вас есть достаточный уровень оптимизации.
  2. Более того, они, вероятно, этого не сделают, если вы сделаете компиляцию релиза с отключенной оптимизацией для целей тестирования (что, по общему признанию, случается редко); и они почти наверняка не будут этого делать во время отладки - тем самым выполняя десятки или сотни операторов if (DEBUG) во время выполнения; таким образом замедляется выполнение (что является моим принципиальным возражением) и, что менее важно, увеличивается размер вашего исполняемого файла или dll; и, следовательно, время выполнения и компиляции. Джонатан, однако, сообщает мне, что его метод можно заставить вообще не компилировать операторы.

В современных процессорах предварительной выборки ветки обходятся довольно дорого. Может быть, ничего страшного, если ваше приложение не критично по времени; но если производительность является проблемой, то да, это достаточно серьезное дело, и я бы предпочел выбрать несколько более быстрый отладочный код (и, возможно, более быстрый выпуск, в редких случаях, как отмечалось).

Итак, мне нужен макрос отладочной печати, который не компилируется, если он не должен быть напечатан, но компилируется, если он есть. Мне также нужны уровни отладки, чтобы, например, если бы я хотел, чтобы важные для производительности части кода не печатались в одних случаях, а в другие, я мог бы установить уровень отладки и включить дополнительные отладочные отпечатки. Я нашел способ реализовать уровни отладки, которые определяли, если отпечаток был даже составлен или нет. Я добился этого так:

DebugLog.h:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

#include <stdio.h>
#include <stdarg.h>

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

Использование макросов

Чтобы использовать это, просто выполните:

DEBUGLOG_INIT("afile.log");

Чтобы записать в файл журнала, просто выполните:

DEBUGLOG_LOG(1, "the value is: %d", anint);

Чтобы закрыть его, вы делаете:

DEBUGLOG_CLOSE();

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

Затем, когда вы хотите включить отладочную печать, просто отредактируйте первый #define в файле заголовка, чтобы он сказал, например

#define DEBUG 1

Чтобы операторы журналирования не компилировались ни к чему, выполните

#define DEBUG 0

Если вам нужна информация из часто выполняемого фрагмента кода (т.е. с высоким уровнем детализации), вы можете написать:

 DEBUGLOG_LOG(3, "the value is: %d", anint);

Если вы определите DEBUG равным 3, уровни ведения журнала 1, 2 и 3 будут компилироваться. Если вы установите его на 2, вы получите уровни ведения журнала 1 и 2. Если вы установите его на 1, вы получите только операторы уровня ведения журнала 1.

Что касается цикла do-while, поскольку он оценивает либо одну функцию, либо ничего, вместо оператора if цикл не нужен. Хорошо, осудите меня за использование C вместо C ++ IO (и Qt QString :: arg () - более безопасный способ форматирования переменных, когда и в Qt - это довольно гладко, но требует больше кода, а документация по форматированию не так организована как бы то ни было - но все же я нашел случаи, когда это предпочтительнее), но вы можете поместить любой код в файл .cpp, который хотите. Это также может быть класс, но тогда вам нужно будет создать его экземпляр и не отставать от него, или выполнить new () и сохранить его. Таким образом, вы просто вставляете операторы #include, init и, при необходимости, close в свой источник, и вы готовы начать его использовать. Однако, если вы так склонны, это будет отличный класс.

Раньше я видел много решений, но ни одно из них не соответствовало моим критериям так же, как это.

  1. Его можно расширить, добавив столько уровней, сколько захотите.
  2. Он ничего не компилирует, если не печатает.
  3. Он централизует ввод-вывод в одном удобном для редактирования месте.
  4. Это гибко, используя форматирование printf.
  5. Опять же, это не замедляет запуски отладки, тогда как всегда компилируемые отладочные отпечатки всегда выполняются в режиме отладки. Если вы занимаетесь информатикой, и вам нелегко написать обработку информации, вы можете обнаружить, что используете симулятор, потребляющий ресурсы процессора, чтобы увидеть, например, где отладчик останавливает его с индексом вне диапазона для вектора. Они уже работают очень медленно в режиме отладки. Обязательное выполнение сотен отладочных отпечатков обязательно еще больше замедлит выполнение таких операций. Для меня такие пробежки не редкость.

Не очень важно, но вдобавок:

  1. Не требует взлома для печати без аргументов (например, DEBUGLOG_LOG(3, "got here!");); что позволяет вам использовать, например, Более безопасное форматирование Qt .arg (). Он работает на MSVC и, следовательно, возможно, на gcc. Он использует ## в #define, что, как указывает Леффлер, нестандартно, но широко поддерживается. (Вы можете перекодировать его, чтобы не использовать ##, если необходимо, но вам придется использовать хак, например, он предлагает.)

Предупреждение. Если вы забыли указать аргумент уровня ведения журнала, MSVC безрезультатно утверждает, что идентификатор не определен.

Возможно, вы захотите использовать имя символа препроцессора, отличное от DEBUG, поскольку некоторые источники также определяют этот символ (например, программы, использующие команды ./configure для подготовки к сборке). Мне это казалось естественным, когда я его разрабатывал. Я разработал его в приложении, где DLL используется чем-то другим, и более удобно отправлять распечатки журнала в файл; но изменение его на vprintf () тоже будет работать нормально.

Я надеюсь, что это избавит многих из вас от печали о поиске лучшего способа ведения журнала отладки; или показывает тот, который вы могли бы предпочесть. Я без особого энтузиазма пытался понять это на протяжении десятилетий. Работает в MSVC 2012 и 2015, и, возможно, в gcc; а также, вероятно, работаю над многими другими, но я не тестировал его на них.

Я тоже хочу когда-нибудь сделать потоковую версию этого.

Примечание. Спасибо Леффлеру, который сердечно помог мне лучше отформатировать мое сообщение для StackOverflow.

person CodeLurker    schedule 08.09.2015
comment
Вы говорите, что во время выполнения выполняются десятки или сотни if (DEBUG) операторов, которые не оптимизируются - это наклон на ветряных мельницах. Вся суть описанной мной системы заключается в том, что код проверяется компилятором (важно и автоматически - специальной сборки не требуется), но код отладки не создается вообще, потому что он оптимизирован ( Таким образом, время выполнения не оказывает никакого влияния на размер кода или производительность, поскольку код отсутствует во время выполнения). - person Jonathan Leffler; 10.04.2016
comment
Джонатан Леффлер: Спасибо за указание на мою неправильную формулировку. Я позволил своим мыслям бежать быстрее, чем пальцы, будучи так рад, что это закончилось. Я пересмотрел свои возражения: 1) вы должны доверять им, чтобы они были оптимизированы, что, по общему признанию, должно произойти, если у вас есть достаточный уровень оптимизации. 2) Более того, они не будут, если вы сделаете компиляцию релиза с отключенной оптимизацией для целей тестирования; и они, вероятно, вообще не будут этого делать во время отладки - тем самым выполняя десятки или сотни операторов if (DEBUG) во время выполнения - тем самым увеличивая размер вашего исполняемого файла или DLL, а также время выполнения. - person CodeLurker; 08.11.2016
comment
Для того, чтобы вы могли делать еще одну важную вещь, которую я делаю, вам потребуются уровни отладки. Хотя мне часто не нужно включать их много, некоторые приложения действительно выигрывают от возможности получить высокий уровень детализации критического по времени цикла с помощью простого #define DEBUG 3, а затем вернуться к гораздо меньшему. подробная информация с #define DEBUG 1. Мне никогда не требовалось более трех уровней, и, таким образом, по крайней мере, примерно 1/3 моих отладок компилируется уже при выпуске. Если я недавно использовал уровень 3, они, вероятно, ВСЕ его используют. - person CodeLurker; 08.11.2016
comment
YMMV. Современная система, которую я показал, поддерживает динамическую (во время выполнения) настройку уровней отладки, поэтому вы можете программно решить, какая часть отладки производится во время выполнения. Я обычно использовал уровни 1-9, хотя верхнего предела (или нижнего предела; по умолчанию уровень 0, который обычно отключен, но может быть явно запрошен во время активной разработки, если это необходимо - это не подходит для долгосрочной работы). Я выбрал уровень по умолчанию 3; вещи можно настроить. Это дает мне большой контроль. Если вы действительно не хотите тестировать код отладки в неактивном состоянии, измените альтернативу на ((void)0) - это просто. - person Jonathan Leffler; 08.11.2016
comment
Я не вижу уровней отладки в вашем коде, хотя вы об этом упоминаете. Для этого вам понадобится debug_print (3, fmt, ...). Возможно, вы использовали это в прошлом, но не публиковали. Что касается того, чтобы не выполнять код во время отладки, вы имеете в виду #define DEBUG_TEST ((void) 0)? if ((void) 0) - ошибка компиляции. Кроме того, в функциях отладки ввод-вывод может быть настолько современным, насколько хочется; как я уже заметил. Альтернатива .arg () действительно более современная, чем printf varargs. - person CodeLurker; 08.11.2016
comment
Это нужно для перехода в чат, но я не уверен, как мы настроим чат и оставим здесь ссылку, чтобы другие люди могли ее найти позже. Я надеялся, что SO может предложить это автоматически, но пока этого не произошло. Прототип extern void db_print(int level, const char *fmt,...); имеет уровень отладки в качестве первого аргумента: схема использования макроса: Usage: TRACE((level, fmt, ...)) где "level" is the debugging level which must be operational for the output to appear. (теперь у меня есть версия DB_TRACE(level, fmt, ...), в которой используются одинарные скобки вместо двойных, но в остальном она такая же.) - person Jonathan Leffler; 08.11.2016
comment
Я имел в виду, что вместо #define TRACE(x) do { if (0) db_print x; } while (0) вы должны использовать #define TRACE(x) ((void)0). - person Jonathan Leffler; 08.11.2016
comment
Ах. Было бы полезно прочитать все это. Это довольно длинный пост. Я думаю, что до сих пор в этом есть существенные моменты. Оказывается, ваш, как и мой, можно использовать для компиляции или не компилировать все отладочные отпечатки и может поддерживать уровни; хотя по общему признанию, вы можете компилировать уровни, которые вы не используете, - за определенную плату во время отладки. - person CodeLurker; 08.11.2016
comment
Позвольте нам продолжить это обсуждение в чате. - person Jonathan Leffler; 08.11.2016

Я считаю, что этот вариант темы дает категории отладки без необходимости иметь отдельное имя макроса для каждой категории.

Я использовал этот вариант в проекте Arduino, где пространство программы ограничено 32 КБ, а динамическая память ограничена 2 КБ. Добавление операторов отладки и строк отладки трассировки быстро использует пространство. Поэтому очень важно иметь возможность ограничить трассировку отладки, которая включается во время компиляции, до минимума, необходимого при каждой сборке кода.

debug.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

вызов файла .cpp

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
person user358795    schedule 04.04.2017

Если вам все равно, что вывод идет на стандартный вывод, вы можете использовать это:

int doDebug = DEBUG;  // Where DEBUG may be supplied in compiler command
#define trace if (doDebug) printf

trace("whatever %d, %i\n", arg1, arg2);
person trindflo    schedule 17.06.2021

person    schedule
comment
Какая версия C поддерживает эту нотацию? И, если это сработало, токен, вставляющий все подобные аргументы, означает, что у вас есть только очень ограниченный набор параметров для строки формата, не так ли? - person Jonathan Leffler; 29.10.2009
comment
@ Джонатан: gcc (Debian 4.3.3-13) 4.3.3 - person eyalm; 29.10.2009
comment
ОК - согласен: это задокументировано как старое расширение GNU (раздел 5.17 руководства GCC 4.4.1). Но вам, вероятно, следует задокументировать, что он будет работать только с GCC - или, может быть, мы сделали это между нами в этих комментариях. - person Jonathan Leffler; 29.10.2009
comment
Мое намерение состояло в том, чтобы показать другой стиль использования аргументов и в основном продемонстрировать использование FUNCTION и LINE - person eyalm; 29.10.2009

person    schedule
comment
Благодарим вас за этот фрагмент кода, который может оказать некоторую немедленную ограниченную помощь. правильное объяснение значительно улучшило бы его долгосрочную ценность, показав, почему это хорошее решение проблемы и сделайте его более полезным для будущих читателей, задав другие похожие вопросы. Измените свой ответ, чтобы добавить пояснения, включая сделанные вами предположения. - person Syscall; 20.03.2021