Макрос #define для печати отладки в C?

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

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

195
задан jww 24 February 2019 в 19:17
поделиться

5 ответов

If you use a C99 or later compiler

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

It assumes you are using C99 (the variable argument list notation is not supported in earlier versions). The do { ... } while (0) idiom ensures that the code acts like a statement (function call). The unconditional use of the code ensures that the compiler always checks that your debug code is valid — but the optimizer will remove the code when DEBUG is 0.

If you want to work with #ifdef DEBUG, then change the test condition:

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

And then use DEBUG_TEST where I used DEBUG.

If you insist on a string literal for the format string (probably a good idea anyway), you can also introduce things like __FILE__, __LINE__ and __func__ into the output, which can improve the diagnostics:

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

This relies on string concatenation to create a bigger format string than the programmer writes.

If you use a C89 compiler

If you are stuck with C89 and no useful compiler extension, then there isn't a particularly clean way to handle it. The technique I used to use was:

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

And then, in the code, write:

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

The double-parentheses are crucial — and are why you have the funny notation in the macro expansion. As before, the compiler always checks the code for syntactic validity (which is good) but the optimizer only invokes the printing function if the DEBUG macro evaluates to non-zero.

This does require a support function — dbg_printf() in the example — to handle things like 'stderr'. It requires you to know how to write varargs functions, but that isn't hard:

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

You can also use this technique in C99, of course, but the __VA_ARGS__ technique is neater because it uses regular function notation, not the double-parentheses hack.

Why is it crucial that the compiler always see the debug code?

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

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

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

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

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

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

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 работал, даже если нет аргументов? Например:

  debug_print ("Foo"); 

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

  debug_print ("Foo");

There's one simple, old-fashioned hack:

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

The GCC-only solution shown below also provides support for that.

However, you can do it with the straight C99 system by using:

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

Compared to the first version, you lose the limited checking that requires the 'fmt' argument, which means that someone could try to call 'debug_print()' with no arguments (but the trailing comma in the argument list to fprintf() would fail to compile). Whether the loss of checking is a problem at all is debatable.

GCC-specific technique for a single argument

Some compilers may offer extensions for other ways of handling variable-length argument lists in macros. Specifically, as first noted in the comments by Hugo Ideler, GCC allows you to omit the comma that would normally appear after the last 'fixed' argument to the macro. It also allows you to use ##__VA_ARGS__ in the macro replacement text, which deletes the comma preceding the notation if, but only if, the previous token is a comma:

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

This solution retains the benefit of requiring the format argument while accepting optional arguments after the format.

This technique is also supported by Clang for GCC compatibility.


Why the do-while loop?

What's the purpose of the do while here?

You want to be able to use the macro so it looks like a function call, which means it will be followed by a semi-colon. Therefore, you have to package the macro body to suit. If you use an if statement without the surrounding do { ... } while (0), you will have:

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

Now, suppose you write:

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

Unfortunately, that indentation doesn't reflect the actual control of flow, because the preprocessor produces code equivalent to this (indented and braces added to emphasize the actual meaning):

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

The next attempt at the macro might be:

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

And the same code fragment now produces:

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

And the else is now a syntax error. The do { ... } while(0) loop avoids both these problems.

There's one other way of writing the macro which might work:

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

This leaves the program fragment shown as valid. The (void) cast prevents it being used in contexts where a value is required — but it could be used as the left operand of a comma operator where the do { ... } while (0) version cannot. If you think you should be able to embed debug code into such expressions, you might prefer this. If you prefer to require the debug print to act as a full statement, then the do { ... } while (0) version is better. Note that if the body of the macro involved any semi-colons (roughly speaking), then you can only use the do { ... } while(0) notation. It always works; the expression statement mechanism can be more difficult to apply. You might also get warnings from the compiler with the expression form that you'd prefer to avoid; it will depend on the compiler and the flags you use.


TPOP was previously at http://plan9.bell-labs.com/cm/cs/tpop and http://cm.bell-labs.com/cm/cs/tpop but both are now (2015-08-10) broken.


Code in GitHub

If you're curious, you can look at this code in GitHub in my SOQ (Stack Вопросы о переполнении) в виде файлов debug.c , debug.h и mddebug.c в папке src / libsoq подкаталог.

393
ответ дан 23 November 2019 в 05:21
поделиться

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

#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
10
ответ дан 23 November 2019 в 05:21
поделиться

Я бы сделал что-нибудь вроде

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

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

9
ответ дан 23 November 2019 в 05:21
поделиться

Для переносимой реализации (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;
}
11
ответ дан 23 November 2019 в 05:21
поделиться
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
7
ответ дан 23 November 2019 в 05:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: