#define макрос для отладочной печати в C? - PullRequest
192 голосов
/ 29 октября 2009

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

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

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

Ответы [ 12 ]

386 голосов
/ 29 октября 2009

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

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

Предполагается, что вы используете C99 (нотация списка аргументов переменной не поддерживается в более ранних версиях). Идиома do { ... } while (0) гарантирует, что код действует как оператор (вызов функции). Безусловное использование кода гарантирует, что компилятор всегда проверяет правильность вашего кода отладки & mdash; но оптимизатор удалит код, когда DEBUG равен 0.

Если вы хотите работать с #ifdef DEBUG, измените условие теста:

#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif

А затем используйте DEBUG_TEST, где я использовал DEBUG.

Если вы настаиваете на строковом литерале для строки формата (вероятно, хорошая идея в любом случае), вы также можете ввести в вывод такие вещи, как __FILE__, __LINE__ и __func__, что может улучшить диагностику:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

Это основано на конкатенации строк для создания строки большего формата, чем пишет программист.

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

Если вы застряли с C89 и не имеете полезного расширения компилятора, то нет особо чистого способа справиться с этим. Техника, которую я использовал, была:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

А потом в коде напишите:

TRACE(("message %d\n", var));

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

Для этого требуется функция поддержки & mdash; dbg_printf () в примере & mdash; обрабатывать такие вещи, как «stderr». Требуется, чтобы вы знали, как писать функции varargs, но это не сложно:

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

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

Конечно, вы также можете использовать эту технику в C99, но техника __VA_ARGS__ аккуратнее, потому что она использует обычные функции, а не взламывает двойные скобки.

Почему так важно, чтобы компилятор всегда видел код отладки?

[ Перефразирование комментариев к другому ответу. ]

Одна из основных идей, лежащих в основе описанных выше реализаций C99 и C89, заключается в том, что компилятор всегда видит отладочные операторы, похожие на printf. Это важно для долгосрочного кода & mdash; код, который будет длиться десятилетие или два.

Предположим, что часть кода была в основном неактивной (стабильной) в течение ряда лет, но теперь ее необходимо изменить. Вы снова включаете трассировку отладки, но разочаровывает необходимость отлаживать код отладки (трассировки), поскольку он ссылается на переменные, которые были переименованы или перепечатаны в годы стабильного обслуживания. Если компилятор (постпроцессор) всегда видит оператор печати, он гарантирует, что любые окружающие изменения не сделали диагностику недействительной. Если компилятор не видит оператор печати, он не может защитить вас от вашей собственной небрежности (или небрежности ваших коллег или сотрудников). См. « Практика программирования » Кернигана и Пайка, особенно главу 8 (см. Также Википедию по TPOP ).

Это «был там, сделал это», опыт & ndash; Я в основном использовал технику, описанную в других ответах, где не отладочная сборка не видит подобные printf операторы в течение ряда лет (более десяти лет). Но я натолкнулся на совет в TPOP (см. Мой предыдущий комментарий), а затем через несколько лет включил некоторый код отладки и столкнулся с проблемами изменения контекста, нарушающими отладку. Несколько раз проверка печати всегда спасала меня от дальнейших проблем.

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

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

debug.h - версия 1.2 (1990-05-01)

/*
@(#)File:            $RCSfile: debug.h,v $
@(#)Version:         $Revision: 1.2 $
@(#)Last changed:    $Date: 1990/05/01 12:55:39 $
@(#)Purpose:         Definitions for the debugging system
@(#)Author:          J Leffler
*/

#ifndef DEBUG_H
#define DEBUG_H

/* -- Macro Definitions */

#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)
#endif /* DEBUG */

/* -- Declarations */

#ifdef DEBUG
extern  int     debug;
#endif

#endif  /* DEBUG_H */

debug.h - версия 3.6 (2008-02-11)

/*
@(#)File:           $RCSfile: debug.h,v $
@(#)Version:        $Revision: 3.6 $
@(#)Last changed:   $Date: 2008/02/11 06:46:37 $
@(#)Purpose:        Definitions for the debugging system
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product:        :PRODUCT:
*/

#ifndef DEBUG_H
#define DEBUG_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);

#endif /* DEBUG_H */

Вариант с одним аргументом для C99 или новее

Кайл Брандт спросил:

В любом случае, чтобы сделать это, debug_print все еще работает, даже если нет аргументов? Например:

    debug_print("Foo");

Есть один простой старомодный хак:

debug_print("%s\n", "Foo");

Показанное ниже решение только для GCC также обеспечивает поддержку.

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

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

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

GCC-специфическая техника для одного аргумента

Некоторые компиляторы могут предлагать расширения для других способов обработки списков аргументов переменной длины в макросах. В частности, как впервые отмечено в комментариях Хьюго Иделера , GCC позволяет вам пропустить запятую, которая обычно появляется после последнего «фиксированного» аргумента макроса. Это также позволяет вам использовать ##__VA_ARGS__ в тексте замены макроса, который удаляет запятую, предшествующую нотации, если, но только если предыдущий токен является запятой:

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

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

Этот метод также поддерживается Clang для совместимости с GCC.


Почему цикл do-while?

Какова цель do while здесь?

Вы хотите иметь возможность использовать макрос так, чтобы он выглядел как вызов функции, что означает, что за ним следует точка с запятой. Поэтому вы должны упаковать тело макроса в соответствии с вашими требованиями. Если вы используете оператор if без окружения do { ... } while (0), вы получите:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) fprintf(stderr, __VA_ARGS__)

Теперь предположим, что вы пишете:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

К сожалению, этот отступ не отражает фактическое управление потоком, потому что препроцессор производит код, эквивалентный этому (отступ и скобки добавлены, чтобы подчеркнуть фактическое значение):

if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

Следующая попытка макроса может быть следующей:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

И тот же фрагмент кода теперь выдает:

if (x > y)
    if (DEBUG)
    {
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    }
; // Null statement from semi-colon after macro
else
    do_something_useful(x, y);

А else теперь является синтаксической ошибкой. Цикл do { ... } while(0) позволяет избежать обеих этих проблем.

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

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

Это оставляет фрагмент программы, показанный как действительный. Приведение (void) предотвращает его использование в контекстах, где требуется значение & mdash; но его можно использовать в качестве левого операнда оператора запятой, где версия do { ... } while (0) не может. Если вы думаете, что сможете встраивать отладочный код в такие выражения, вы можете предпочесть это. Если вы предпочитаете, чтобы отладочная печать действовала как полный оператор, тогда лучше использовать версию do { ... } while (0). Обратите внимание, что если тело макроса включает в себя любые точки с запятой (грубо говоря), то вы можете использовать только нотацию do { ... } while(0). Это всегда работает; механизм выражения выражения может быть более сложным для применения. Вы также можете получить предупреждения от компилятора с формой выражения, которую вы предпочитаете избегать; это будет зависеть от компилятора и используемых вами флагов.


TPOP ранее был на http://plan9.bell -labs.com / cm / cs / tpop и http://cm.bell -labs.com / cm / cs / tpop , но сейчас оба (2015-08-10) сломан.


Код в GitHub

Если вам интересно, вы можете посмотреть этот код в GitHub в моем SOQ (Stack Переполнение Вопросы) хранилище в виде файлов debug.c, debug.h и mddebug.c в ЦСИ / libsoq подкаталог.

28 голосов
/ 19 сентября 2011

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

#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
11 голосов
/ 29 октября 2009

Для переносимой (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;
}
10 голосов
/ 29 октября 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
9 голосов
/ 29 октября 2009

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

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

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

7 голосов
/ 29 октября 2009
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
6 голосов
/ 09 февраля 2012

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

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

1 голос
/ 11 октября 2018

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

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

1 голос
/ 06 сентября 2016

Мой любимый из приведенных ниже 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)
1 голос
/ 08 сентября 2015

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

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

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

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

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

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

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

Не очень важно, но в дополнение:

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

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

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

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

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

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

...