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

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

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

Как это делается с макросом?

Ответ 1

Если вы используете компилятор 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__ такие вещи, как __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 (вопросы о переполнении стека) в виде файлов debug.c, debug.h и mddebug.c в mddebug.c src/libsoq.

Ответ 2

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

#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

Ответ 3

Для переносимой (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;
}

Ответ 4

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

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

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

Ответ 5

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

#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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

var_dump("%d", count);

производит вывод, например:

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

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

код

#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)

Ответ 9

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

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

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

Ответ 10

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

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

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

хотя в настоящее время это даже не нужно, технически говоря, поскольку это ничего не делает. Я все еще использую ЗАКРЫТЬ прямо сейчас, однако, на случай, если я передумаю о том, как это работает, и хочу оставить файл открытым между инструкциями регистрации.

Затем, когда вы хотите включить отладочную печать, просто отредактируйте первый #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 и, возможно, закрываете их в свой источник, и вы готовы начать их использовать. Однако, если бы вы были так склонны, это было бы хорошим уроком.

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

  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; а также, вероятно, работает над многими другими, но я не проверял это на них.

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

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

Ответ 11

Итак, при использовании 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 в качестве имени переменной, все будет в порядке.

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

Ответ 12

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

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

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");
...