Якщо ви використовуєте компілятор C99 або новішої версії
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Він передбачає, що ви використовуєте C99 (позначення списку змінних аргументів не підтримується в попередніх версіях). У do { ... } while (0)
ідіома гарантує , що код діє як (виклик функції) заяву. Безумовне використання коду гарантує, що компілятор завжди перевіряє, що ваш код налагодження є дійсним - але оптимізатор видалить код, коли DEBUG дорівнює 0.
Якщо ви хочете працювати з #ifdef DEBUG, то змініть умову тесту:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
А потім використовуйте DEBUG_TEST там, де я використовував DEBUG.
Якщо ви наполягаєте на строковий літерал для рядка формату (ймовірно, хороша ідея в будь-якому випадку), ви можете також ввести такі речі , як __FILE__
, __LINE__
і __func__
в продукції, яка може поліпшити діагностику:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Це покладається на конкатенацію рядків, щоб створити рядок більшого формату, ніж пише програміст.
Якщо ви використовуєте компілятор C89
Якщо ви застрягли в C89 і не маєте корисного розширення компілятора, не існує особливо чистого способу впоратися з цим. Я використовував техніку:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
А потім у коді напишіть:
TRACE(("message %d\n", var));
Подвійні круглі дужки мають вирішальне значення - і саме тому ви маєте смішні позначення в макророзширенні. Як і раніше, компілятор завжди перевіряє код на синтаксичну достовірність (що добре), але оптимізатор викликає функцію друку лише в тому випадку, якщо макрос DEBUG оцінюється як ненульовий.
Для цього потрібна функція підтримки - dbg_printf () у прикладі - для обробки речей, таких як 'stderr'. Це вимагає, щоб ви знали, як писати функції varargs, але це не важко:
#include <stdarg.h>
#include <stdio.h>
void dbg_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
Ви, звичайно, також можете використовувати цю техніку в C99, але ця __VA_ARGS__
методика акуратніша, оскільки вона використовує регулярні позначення функцій, а не подвійні дужки.
Чому важливо, щоб компілятор завжди бачив код налагодження?
[ Переглядання коментарів, зроблених до іншої відповіді. ]
Однією з головних ідей, що стоять перед реалізаціями C99 і C89, наведеними вище, є те, що власник компілятора завжди бачить налагодження операцій, схожих на printf. Це важливо для довгострокового коду - коду, який триватиме десять-два десятки.
Припустимо, фрагмент коду протягом багатьох років спокійно (стабільно), але зараз його потрібно змінити. Ви знову вмикаєте відстежувальний налагодження, але засмучувати налагоджувальний (відстежуючий) код неприємно, тому що він стосується змінних, які були перейменовані або повторно введені протягом років стабільного обслуговування. Якщо компілятор (після попереднього процесора) завжди бачить оператор друку, він гарантує, що будь-які оточуючі зміни не визнали недійсною діагностику. Якщо компілятор не бачить твердження про друк, він не може захистити вас від вашої власної необережності (або недбалості ваших колег чи співробітників). Див. " Практика програмування " Керніган та Пайк, особливо Глава 8 (див. Також Вікіпедію на ТПОП ).
Це досвід "там, зробили це" - я по суті використовував методику, описану в інших відповідях, коли збірка без налагодження не бачить висловлювань, схожих на 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 методика для одного аргументу
Деякі компілятори можуть запропонувати розширення для інших способів обробки списків аргументів змінної довжини в макросах. Зокрема, як вперше зазначалося в коментарях Hugo Ideler , GCC дозволяє опустити кому, яка зазвичай з'являється після останнього "виправленого" аргументу в макрос. Він також дозволяє використовувати ##__VA_ARGS__
в тексті заміни макросу, який видаляє кому, що передує нотації, якщо, але тільки якщо попередній маркер є комою:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Це рішення зберігає перевагу вимагати аргументу формату, приймаючи необов'язкові аргументи після формату.
Ця методика також підтримується Clang для сумісності з GCC.
Чому цикл "робити час"?
Яка мета 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 (Stack Overflow) Питання сховища в вигляді файлів debug.c
, debug.h
і mddebug.c
в
SRC / libsoq
підкаталозі.