#define макрос для налагодження друку в C?


209

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

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

Як це здійснюється за допомогою макроса?


Чи буде оптимізатор компілятора (gcc) оптимізувати заяви, як, наприклад, (DEBUG) {...}, якщо у виробничому коді макрос DEBUG встановлений на 0? Я розумію, що є вагомі причини залишати заяви про налагодження видимими для компілятора, але погані відчуття залишаються. -Пат
Пат

Відповіді:


410

Якщо ви використовуєте компілятор 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 підкаталозі.


1
Я думаю, що GCC ## - підхід від gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html варто було б згадати під заголовком "Один варіант аргументу C99".
Хьюго Іделер

2
Роками пізніше, і ця відповідь все ще є найкориснішою з усіх мереж, як псевдоніми printk! vfprintf не працює в просторі ядра, оскільки stdio недоступний. Дякую! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

6
У вашому прикладі з ключовими словами __FILE__, __LINE__, __func__, __VA_ARGS__він не буде компілюватися, якщо у вас немає параметрів printf, тобто якщо ви просто зателефонували debug_print("Some msg\n"); Ви можете виправити це за допомогою fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ дозволяє передавати параметри не функції.
mc_electron

1
@LogicTom: різниця між #define debug_print(fmt, ...)і #define debug_print(...). Перший із них вимагає щонайменше одного аргументу, рядка формату ( fmt) та нуля чи більше інших аргументів; другий загалом вимагає нуля або більше аргументів. Якщо ви використовуєте debug_print()перший, ви отримуєте помилку від препроцесора про неправильне використання макросу, тоді як другий - ні. Однак ви все ще отримуєте помилки компіляції, оскільки текст заміни не є дійсним C. Отже, це насправді не є великою різницею - отже, використання терміна "обмежена перевірка".
Джонатан Леффлер

1
Варіант, показаний на abover, @ St.Antario, використовує єдиний активний рівень налагодження у всій програмі, і я зазвичай використовую параметри командного рядка, щоб дозволити встановлювати рівень налагодження під час запуску програми. У мене також є варіант, який розпізнає декілька різних підсистем, кожній з яких присвоюється ім'я та власний рівень налагодження, так що я можу використовувати -D input=4,macros=9,rules=2для встановлення рівня налагодження вхідної системи 4, а системи макросів - 9 (піддаючи інтенсивному контролю) ) і система правил до 2. Існують нескінченні варіації теми; використовуйте все, що вам підходить.
Джонатан Леффлер

28

Я використовую щось подібне:

#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
Що стосується for(;0;), це може створити проблему, коли ви пишете щось на кшталт D continue;або D break;.
Аккреатор

1
Отримав мене; все ж здається дуже малоймовірним, що це може статися випадково.
mbq

11

Для портативної реалізації (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;
}

3
@ LB: щоб змусити препроцесора "подумати", існує лише один аргумент, дозволяючи _ розширити на подальшому етапі.
Марцін Козюк

10

Ось версія, яку я використовую:

#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

Я б робив щось подібне

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

Я думаю, що це чистіше.


Мені не дуже подобається ідея використання макросу в тесті як прапор. Чи можете ви пояснити, чому друк налагодження слід завжди перевіряти?
LB40

1
@Jonathan: Якщо код коли-небудь виконується в режимі налагодження, навіщо вам турбуватися, якщо він компілюється в режимі не налагодження? assert()від stdlib працює так само, і я зазвичай просто повторно використовую NDEBUGмакрос для власного коду налагодження ...
Крістоф

використовуючи DEBUG у тесті, якщо хтось робить неконтрольований DEBUG, ваш код більше не компілюється. правильно?
LB40

4
Увімкнути налагодження неприємно, а потім доведеться налагоджувати налагоджувальний код, оскільки він стосується змінних, які були перейменовані або повторно введені і т.д. не визнав недійсним діагностику. Якщо компілятор не бачить твердження про друк, він не може захистити вас від вашої власної необережності (або недбалості ваших колег чи співробітників). Див. "Практика програмування" Керніган та Пайк - plan9.bell-labs.com/cm/cs/tpop .
Джонатан Леффлер

1
@Christoph: ну, начебто ... Я використовую NDEBUG для керування лише твердженнями, а окремий макрос (як правило, DEBUG) для управління відстеженням налагодження. Я часто не хочу, щоб вихід налагодження відображався беззастережно, тому у мене є механізм контролю, чи з’являється вихід (рівні налагодження, і замість виклику fprintf () безпосередньо я викликаю функцію друку налагодження, яка друкує лише умовно, таку ж збірку код може друкувати або не друкувати на основі програмних параметрів). Я виступаю за те, щоб компілятор для всіх складових повинен бачити діагностичні твердження; однак він не буде генерувати код, якщо налагодження не включено.
Джонатан Леффлер

8

Відповідно до http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , раніше повинно бути ##попереднє __VA_ARGS__.

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


1
Ласкаво просимо до переповнення стека. Ви правильні, що GCC має нестандартне розширення, на яке ви посилаєтесь. В даний час прийнята відповідь насправді згадує це, включаючи саме вказану посилальну URL-адресу.
Джонатан Леффлер

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

Яка версія C підтримує цю нотацію? І якщо це спрацювало, токен, що вставляє всі подібні аргументи, означає, що у вас є лише обмежений набір параметрів для рядка формату, чи не так?
Джонатан Леффлер

@Jonathan: gcc (Debian 4.3.3-13) 4.3.3
eyalm

1
Гаразд - погоджено: це задокументовано як старе розширення GNU (розділ 5.17 керівництва GCC 4.4.1). Але вам, мабуть, слід документувати, що він буде працювати тільки з GCC - а може, ми це зробили між нами в цих коментарях.
Джонатан Леффлер

1
Мій намір полягав у тому, щоб показати інший стиль використання арг і, головним чином, продемонструвати використання FUNCTION та LINE
eyalm

2

Це те, що я використовую:

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

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


Краще, щоб компілятор завжди перевіряв код налагодження.
Джонатан Леффлер

1

Моє улюблене нижче 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

Отже, при використанні 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

Я багато років готувався до того, як це зробити, і нарешті придумав рішення. Однак я не знав, що тут вже є інші рішення. По-перше, на відміну від відповіді Леффлера , я не бачу його аргументу про те, що відбитки налагодження завжди слід збирати. Я вважаю за краще, щоб у моєму проекті не було виконано тонни непотрібних кодів у випадках, коли мені потрібно тестувати, і вони, можливо, не будуть оптимізовані.

Не компіляція кожного разу може здатися гіршою, ніж це є у фактичній практиці. Ви закінчуєте налагодження відбитків, які іноді не компілюються, але скласти і протестувати їх не так вже й важко, перш ніж завершити проект. У цій системі, якщо ви використовуєте три рівні налагодження, просто поставте її на рівень третього повідомлення про налагодження, виправте помилки компіляції та перевірте, чи немає інших, перш ніж доопрацювати код yer. (Звичайно, компіляція заяв про налагодження не є гарантією того, що вони все ще працюють за призначенням.)

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

На відміну від вартості додатковий крок тестування їх, щоб побачити, що вони будуть складати перед доставкою, це те

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

хоча в даний час це навіть не потрібно, технічно кажучи, оскільки це нічого не робить. Зараз я все ще використовую ЗАКРИТИ, якщо я передумав, як він працює, і захочу залишити файл відкритим між операторами реєстрації.

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

Раніше я бачив багато рішень, але жоден не відповідав моїм критеріям, як і цей.

  1. Він може бути розширений, щоб робити скільки завгодно рівнів.
  2. Вона компілюється ні до чого, якщо не друкується.
  3. Він централізує IO в одному простому для редагування місці.
  4. Це гнучко, використовуючи форматування printf.
  5. Знову ж таки, це не сповільнює запуски налагодження, тоді як відбитки налагодження, що завжди складаються, завжди виконуються в режимі налагодження. Якщо ви займаєтеся інформатикою, і не легше писати обробку інформації, ви можете виявити, що ви працюєте з симулятором, що споживає процесор, щоб побачити, наприклад, де налагоджувач зупиняє його з індексом поза діапазоном для вектора. Вони вже працюють дуже повільно в режимі налагодження. Обов’язкове виконання сотень відладок відбитків обов'язково сповільнить такі пробіги ще більше. Для мене такі пробіжки не рідкість.

Не страшенно важливо, але на додачу:

  1. Друкувати без аргументів не потрібно (наприклад DEBUGLOG_LOG(3, "got here!");); таким чином, ви можете використовувати, наприклад, більш безпечне форматування .arg () Qt. Він працює на MSVC, і, отже, ймовірно gcc. Він використовує ##в #defines, що є нестандартним, як зазначає Леффлер, але широко підтримується. (Ви можете перекодувати його не використовувати ##при необхідності, але вам доведеться використовувати хак, як він надає.)

Попередження: Якщо ви забудете надати аргумент рівня реєстрації, MSVC недоброзичливо стверджує, що ідентифікатор не визначений.

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

Я сподіваюся, що це позбавить багатьох із вас скорботи щодо з'ясування найкращого способу зробити налагодження журналу; або показує вам той, який ви можете віддати перевагу. Я напівсердечно намагаюся розібратися в цьому десятиліттями. Працює в MSVC 2012 та 2015, і, ймовірно, на gcc; а також, ймовірно, працюю над багатьма іншими, але я не перевіряв цього на них.

Я маю на увазі зробити поточну версію цього дня також.

Примітка: Дякую Леффлеру, який сердечно допоміг мені краще відформатувати моє повідомлення для StackOverflow.


2
Ви кажете "виконувати десятки чи сотні if (DEBUG)заяв під час виконання, які не оптимізуються", - це нахили на вітряках . Весь сенс цієї системи я описав, що код перевіряється компілятором (важливо, і автоматичний - не потрібна спеціальна збірка) , але код налагодження не генеруються на всіх , тому що це оптимізоване (так що нульове вплив виконання на розмір коду або продуктивність, оскільки код не присутній під час виконання).
Джонатан Леффлер

Джонатан Леффлер: Thx за вказівку моїх неправильних формулювань. Я дозволяю, щоб мої думки бігали швидше, ніж мої пальці, дуже раді, що це закінчилося. Я переглянув свої заперечення з "... 1) ви повинні довіряти їм, щоб отримати оптимізацію, що, мабуть, має статися, якщо у вас достатній рівень оптимізації. вимкнено для тестування, і вони, ймовірно, взагалі не стануть під час налагодження - тим самим виконуючи десятки чи сотні операторів "if (DEBUG)" під час виконання - тим самим збільшуючи розмір виконуваного файлу або dll та час його виконання ".
CodeLurker

Для того, щоб ваші робили інше важливе, що робить моя, вам доведеться мати рівні налагодження. Хоча мені часто не потрібно багато з них увімкнено, деякі додатки дійсно отримують користь від того, щоб мати можливість отримати чудовий рівень деталізації критичного циклу за допомогою простого "#define DEBUG 3", а потім повернутися до набагато менше детальної інформації з "#define DEBUG 1". Мені ніколи не було потрібно більше трьох рівнів, і, таким чином, щонайменше приблизно 1/3 моїх налагоджень складаються вже при випуску. Якщо я нещодавно використовував рівень 3, вони, ймовірно, ВСІ.
CodeLurker

YMMV. Сучасна система, яку я показав, підтримує динамічне (час виконання) налаштування рівнів налагодження, тому ви можете програмно вирішити, яка частина налагодження виробляється під час виконання. Зазвичай я використовував рівні 1-9, хоча немає верхньої межі (або нижньої межі; за замовчуванням рівень 0, який зазвичай вимкнено, але його можна чітко запитати під час активної розробки, якщо це доречно - це не підходить для тривалої роботи). Я вибрав рівень за замовчуванням 3; речі можна налаштувати. Це дає мені великий контроль. Якщо ви дійсно не хочете перевіряти код налагодження, коли неактивний, змінити альтернативу на ((void)0)- це просто.
Джонатан Леффлер

1
А-а-а. Це допомогло б прочитати всю річ. Це досить довгий пост. Я думаю, що це поки що має суттєві моменти. Виявляється, ваш, як і мій, може використовуватися для компіляції або не компіляції всіх відбитків налагодження, і може підтримувати рівні; Хоча, правда, ваш може компілювати рівні, які ви не використовуєте - за ціною під час налагодження.
CodeLurker

0

Я вважаю, що ця варіація теми дає налагодження категорій без необхідності мати окреме ім’я макросів для кожної категорії.

Я використав цю варіацію в проекті Arduino, де програмний простір обмежений 32 К, а динамічна пам'ять обмежена 2 К. Додавання операторів налагодження та рядків налагодження трасування швидко використовує простір. Тому важливо мати можливість обмежувати відлагоджувальну помилку, яка включається під час компіляції, до мінімально необхідного при кожному побудові коду.

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");
...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.