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

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

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

Как это выполняется с макросом?

11 ответов


если вы используете компилятор C99

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

предполагается, что вы используете C99 (нотация списка переменных аргументов не поддерживается в более ранних версиях). The 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));

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

для этого требуется функция поддержки - 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-like. Это важно для долгосрочного кода-кода, который будет длиться десятилетие или два.

предположим, что часть кода была в основном спящей (стабильной) в течение нескольких лет, но теперь ее нужно изменить. Вы повторно включите отладка трассировки-но неприятно отлаживать код отладки (трассировки), потому что он ссылается на переменные, которые были переименованы или перепечатаны за годы стабильного обслуживания. Если компилятор (post pre-processor) всегда видит инструкцию print, это гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит инструкцию print, он не может защитить вас от вашей собственной небрежности (или небрежности ваших коллег или соучастники.) Смотри'практика программирования ' от Кернигана и пайка, особенно Глава 8 (см. Также Википедию на обработка поставки третьему лицу).

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

я использую NDEBUG только для управления утверждениями и отдельный макрос (обычно DEBUG) для управления встроением трассировки отладки в программу. Даже когда трассировка отладки встроена, я часто не хочу, чтобы вывод отладки отображался безоговорочно, поэтому у меня есть механизм для управления появлением вывода (уровни отладки, и вместо вызова fprintf () напрямую, я вызываю функцию печати отладки, которая только условно печатает, так что та же сборка кода может печатать или не печатать на основе параметров программы). У меня также есть версия кода с несколькими подсистемами для больших программ, так что у меня могут быть разные разделы программы, производящие разное количество трассировки под контролем времени выполнения.

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

"отладка".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 */

"отладка".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-only также обеспечивает поддержку для этого.

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

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

по сравнению с первой версией вы теряете ограниченную проверку, которая требует аргумента "fmt", что означает, что кто-то может называть debug_print () без аргументов. Является ли потеря проверки вообще проблемой, спорно.

GCC-специфическая техника

некоторые компиляторы могут предлагать расширения для других способов обработки списков аргументов переменной длины в макросах. В частности, как впервые отмечено в комментариях Угу Ideler, GCC позволяет опустить запятую, которая обычно появляется после последнего "фиксированного" аргумента макроса. Он также позволяет использовать ##__VA_ARGS__ в тексте замены макроса, который удаляет запятую, предшествующую обозначению if, но только if, предыдущий маркер является запятой:

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)

это решение сохраняет преимущество требования аргумента format при принятии необязательных аргументов после формата.

эта техника также поддерживается лязгом для совместимости с 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 теперь синтаксическая ошибка. The do { ... } while(0) loop избегает обеих этих проблем.

есть еще один способ написания макроса, который может сработать:

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

это оставляет фрагмент программы показан как допустимый. The (void) cast предотвращает его использование в контекстах, где требуется значение , но его можно использовать как левый операнд оператора запятой, где 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 (стек Переполнение вопросов) репозиторий в виде файлов debug.c, debug.h и mddebug.c в src / libsoq подкаталог.


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

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

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

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

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

EDIT: хорошо, это может создать проблему, когда есть else где-то рядом, что может быть перехвачен этого вводят if. Это версия, которая идет по нему:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif

для портативной (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;
}

или (hackish, не рекомендовал бы его)

#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;
}

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

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

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


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

#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

#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)

по данным http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html, должно быть ## до __VA_ARGS__.

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


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

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

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


мой любимый из ниже var_dump, который при вызове в качестве:

var_dump("%d", count);

выпускает продукцию как:

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

кредит @ "Jonathan Leffler". Все 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)

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

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

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

  1. вы должны доверять им, чтобы оптимизироваться, что, по общему признанию, должно произойти, если у вас есть достаточный уровень оптимизации.
  2. кроме того, они, вероятно, не если вы делаете релиз компиляции с оптимизацией выключен в целях тестирования (что правда редко), и они почти наверняка не во время отладки, тем самым выполнив десятки или сотни "если (отладка)" заявления во время выполнения, поэтому замедление исполнения (это мой принцип возражения), и менее важно, увеличивая свой исполняемый файл или 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);
}

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

Для использовать его, просто do:

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 тоже - это довольно гладко, но занимает больше кода, и документация по форматированию не так организована, как могла бы быть, но все же я нашел случаи, когда это предпочтительнее), но вы можете поместить любой код в Qt .cpp файл, который вы хотите. Это также может быть класс, но тогда вам понадобится чтобы создать экземпляр и идти в ногу с ним, или сделать новый() и сохранить его. Таким образом, вы просто отбрасываете операторы #include, init и необязательно close в свой источник, и вы готовы начать его использовать. Однако это был бы прекрасный класс, если вы так склонны.

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

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

кроме того,

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

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

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

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


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

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

"отладка".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");
...