C # define makro for debug printing

Próba utworzenia makra, które może być użyte do wypisywania wiadomości debugowania, gdy Debug jest zdefiniowany, jak następujący pseudo kod:

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

Jak to się robi z makrem?

Author: Jonathan Leffler, 2009-10-29

11 answers

Jeśli używasz kompilatora C99

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

Zakłada, że używasz C99 (notacja listy argumentów zmiennej nie jest obsługiwana we wcześniejszych wersjach). Idiom do { ... } while (0) zapewnia, że kod działa jak instrukcja (wywołanie funkcji). Bezwarunkowe użycie kodu zapewnia, że kompilator zawsze sprawdza, czy kod debugowania jest poprawny - ale optymalizator usunie kod, gdy debugowanie będzie równe 0.

Jeśli chcesz pracować z debugowaniem # ifdef, Zmień test stan:

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

A następnie użyj DEBUG_TEST gdzie użyłem DEBUG.

Jeśli nalegasz na literalny ciąg znaków dla formatu string (pewnie i tak dobry pomysł), możesz również wprowadzić takie rzeczy, jak __FILE__, __LINE__ i __func__ do wyjścia, które może poprawić diagnostykę:

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

Polega to na konkatenacji ciągów znaków, aby utworzyć ciąg w większym formacie niż pisze programista.

Jeśli używasz kompilatora C89

Jeśli utkniesz z C89 i nie będziesz użyteczny rozszerzenie kompilatora, to nie ma specjalnie czystego sposobu na jego obsługę. Technika, której użyłem to:

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

A następnie w kodzie napisz:

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

Podwójne nawiasy są kluczowe - i dlatego masz zabawny zapis w ekspansji makr. Tak jak poprzednio, kompilator zawsze sprawdza poprawność składniową kodu (co jest dobre), ale optymalizator wywołuje funkcję drukowania tylko wtedy, gdy makro debuguje do wartości niezerowej.

To wymaga funkcja wsparcia-dbg_printf () w przykładzie-do obsługi takich rzeczy jak 'stderr'. To wymaga, aby wiedzieć, jak pisać funkcje varargs, ale to nie jest trudne: {]}

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

Możesz również użyć tej techniki w C99, oczywiście, ale technika __VA_ARGS__ jest lepsza, ponieważ używa regularnej notacji funkcji, a nie hack podwójnego nawiasu.

Dlaczego tak ważne jest, aby kompilator zawsze widział kod debugowania?

[ponowne dodawanie komentarzy do innych odpowiedz.]

Jedną z głównych idei stojących za implementacjami C99 i C89 powyżej jest to, że właściwy kompilator zawsze widzi debugujące instrukcje podobne do printf. Jest to ważne w przypadku długofalowego kodowania, które potrwa dekadę lub dwie.

Załóżmy, że fragment kodu był w większości uśpiony (stabilny) przez wiele lat, ale teraz musi zostać zmieniony. Ponownie włączasz debugowanie śledzenia - ale frustrujące jest to, że musisz debugować kod debugowania (śledzenia), ponieważ odnosi się do zmienne, które zostały zmienione lub przepisane w latach stabilnej konserwacji. Jeśli kompilator (post Pre-procesor) zawsze widzi instrukcję print, zapewnia, że wszelkie otaczające zmiany nie unieważniły diagnostyki. Jeśli kompilator nie widzi instrukcji print, nie może ochronić Cię przed własną niedbałością (lub niedbałością twoich kolegów lub współpracowników). Zobacz "The Practice of Programming "Kernighan' a i Pike ' a, szczególnie Rozdział 8 (Zobacz też Wikipedia na TPOP).

Jest to doświadczenie "been there, done that" - użyłem zasadniczo techniki opisanej w innych odpowiedziach, gdzie non-debug build nie widzi instrukcji podobnych do printf przez wiele lat (ponad dekadę). Ale natknąłem się na porady w TPOP (patrz mój poprzedni komentarz), a następnie włączył trochę debugowania kodu po kilku latach i napotkał problemy ze zmienionym kontekstem łamania debugowania. Kilka razy, mając druk zawsze validated uratował mnie od późniejszych problemów.

Używam NDEBUG tylko do kontrolowania twierdzeń, a osobne makro (Zwykle debugowanie) do kontrolowania, czy śledzenie debugowania jest wbudowane w program. Nawet jeśli śledzenie debugowania jest wbudowane, często nie chcę, aby wyjście debugowania pojawiało się bezwarunkowo, więc mam mechanizm kontrolujący, czy wyjście się pojawia (poziomy debugowania, i zamiast wywoływać bezpośrednio fprintf (), wołam funkcję drukowania debugowania, która tylko warunkowo drukuje, więc ta sama konstrukcja kod może drukować lub nie drukować na podstawie opcji programu). Mam również wersję kodu 'MultiSystem' dla większych programów, dzięki czemu mogę mieć różne sekcje programu produkujące różne ilości śledzenia - pod kontrolą runtime.

Zalecam, aby dla wszystkich kompilatorów, kompilator widział instrukcje diagnostyczne; jednak kompilator nie wygeneruje żadnego kodu dla debugujących instrukcji trace, chyba że Debug jest włączony. Zasadniczo oznacza to, że wszystkie Twoje kod jest sprawdzany przez kompilator za każdym razem, gdy kompilujesz - czy to do wydania, czy do debugowania. To dobra rzecz!

Debugowanie.h-Wersja 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 */

Debugowanie.h-Wersja 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 */

Pojedynczy argument C99 variant

Kyle Brandt zapytał:

W każdym razie, aby to zrobić debug_print nadal działa, nawet jeśli nie ma argumentów? Na przykład:

    debug_print("Foo");

Jest jeden prosty, staromodny hack:

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

The GCC-only solution również zapewnia wsparcie dla tego.

Można to jednak zrobić za pomocą prostego systemu C99, używając:]}
#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

W porównaniu z pierwszą wersją, tracisz ograniczone sprawdzanie, które wymaga argumentu 'fmt' , co oznacza, że ktoś może wywołać 'debug_print ()' bez argumentów. Czy utrata kontroli jest problemem w ogóle jest dyskusyjne.

Technika specyficzna dla GCC

Niektóre Kompilatory mogą oferować rozszerzenia dla innych sposobów obsługi listy argumentów o zmiennej długości w makrach. W szczególności, jak po raz pierwszy zauważono w komentarzach Hugo Ideler , GCC pozwala pominąć przecinek, który normalnie pojawiałby się po ostatnim 'stałym' argumencie makra. Umożliwia również korzystanie z##__VA_ARGS__ w tekście zastępczym makra, który usuwa przecinek poprzedzający notację, Jeśli, ale tylko wtedy, gdy poprzedni token jest przecinkiem:

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

To rozwiązanie zachowuje tę zaletę, że wymaga argumentu format, podczas gdy akceptowanie opcjonalnych argumentów po formacie.

Ta technika jest również wspierana przez Clang dla zgodności z GCC.

Dlaczego pętla do-while?

Jaki jest cel do while tutaj?

Chcesz móc używać makra tak, aby wyglądało to jak wywołanie funkcji, co oznacza, że po nim nastąpi dwukropek. Dlatego musisz spakować ciało makro, aby pasowało. Jeśli użyjesz if instrukcji bez otaczającego do { ... } while (0), będziesz miał:

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

Załóżmy, że napiszesz:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);
W przeciwieństwie do innych preprocesorów, preprocesor generuje kod odpowiadający temu (wcięcia i nawiasy dodawane w celu podkreślenia rzeczywistego znaczenia):]}
if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

Kolejna próba makra może być:

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

I ten sam fragment kodu tworzy teraz:

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

I {[28] } jest teraz błędem składni. Pętla do { ... } while(0) unika zarówno te problemy.

Jest jeszcze jeden sposób zapisu makra, który może działać:]}
/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

To pozostawia fragment programu pokazany jako poprawny. Cast (void) zapobiega używaniu go w kontekstach, w których wymagana jest wartość - ale może być używany jako lewy operand operatora przecinka, w którym wersja do { ... } while (0) nie może. Jeśli uważasz, że powinieneś być w stanie osadzić kod debugowania w takich wyrażeniach, możesz to preferować. Jeśli wolisz, aby Wydruk debugowania działał jako pełny statement, wtedy wersja do { ... } while (0) jest lepsza. Zauważ, że jeśli ciało makra zawiera średniki (z grubsza rzecz biorąc), możesz używać tylko notacji do { ... } while(0). Zawsze działa; mechanizm wyrażeń może być trudniejszy do zastosowania. Możesz również otrzymać ostrzeżenia od kompilatora z formą wyrażenia, której wolisz unikać; zależy to od kompilatora i używanych FLAG.


TPOP był wcześniej w http://plan9.bell-labs.com/cm/cs/tpop i http://cm.bell-labs.com/cm/cs/tpop [71]} ale oba są teraz (2015-08-10) zepsute.


Kod w Githubie

Jeśli jesteś ciekawy, możesz spojrzeć na ten kod w Githubie w moim SOQ (Stack Overflow Questions) repozytorium jako pliki debug.c, debug.h i mddebug.c W src / libsoq podkatalog.

 357
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-12 16:07:30

Używam czegoś takiego:

#ifdef DEBUG
 #define D if(1) 
#else
 #define D if(0) 
#endif

Niż używam D jako prefiksu:

D printf("x=%0.3f\n",x);

Kompilator widzi kod debugowania, nie ma problemu z przecinkami i działa wszędzie. Działa również, gdy printf nie wystarczy, powiedzmy, kiedy musisz zrzucić tablicę lub obliczyć jakąś wartość diagnozującą, która jest zbędna dla samego programu.

EDIT: Ok, to może generować problem, gdy jest else gdzieś w pobliżu, które mogą być przechwycone przez ten wstrzykiwany if. Jest to wersja, która przechodzi it:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif
 22
Author: mbq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-22 13:56:37

Dla implementacji przenośnej (ISO C90), można użyć podwójnego nawiasu, jak to;

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

Or (hackish, wouldn ' t recommend it)

#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
Author: Marcin Koziuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 17:06:27

Zrobiłbym coś takiego

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif
Myślę, że to jest czystsze.
 9
Author: LB40,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 16:29:50

Oto wersja, której używam:

#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
 8
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 16:59:49
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
 6
Author: eyalm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-29 16:24:33

Zgodnie z http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , przed __VA_ARGS__ powinno być ##.

W przeciwnym razie makro #define dbg_print(format, ...) printf(format, __VA_ARGS__) nie skompiluje następującego przykładu: dbg_print("hello world");.

 5
Author: Chobits Tai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-27 23:12:47

Tego używam:

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

Ma przyjemną zaletę, aby prawidłowo obsługiwać printf, nawet bez dodatkowych argumentów. W przypadku DBG = = 0, nawet najgłupszy kompilator nie dostaje nic do żucia, więc żaden kod nie jest generowany.

 0
Author: 5tenzel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-05 13:09:42

Moim ulubionym z poniższych jest var_dump, który po wywołaniu jako:

var_dump("%d", count);

Generuje wyjście jak:

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

/ Align = "center"bgcolor =" # e0ffe0 " / Papież / / align = center / Wszyscy są C89-szczęśliwi:

Kod

#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)
 0
Author: Tom Hale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-06 06:57:03

Dusiłem się nad tym, jak to zrobić od lat, a niedawno wymyśliłem to rozwiązanie, zanim zobaczyłem to pytanie. Po pierwsze, przy różnicy z odpowiedzią Lefflera , nie widzę jego argumentu, że wydruki debugowe powinny być zawsze kompilowane. Tak, kończy się drukiem debugowania, który nie kompiluje się czasami, ale nie jest tak trudno skompilować i przetestować je przed finalizacją projektu. Powiedzmy, że nie. najgorsze, co się dzieje, to to, że musisz poprawić kilka garści na duży projekt. Jeśli twój projekt nie jest HUUUUGE, to łatwiejsze niż to.

Moje rozwiązanie przewiduje poziomy szczegółowości debugowania; i jeśli ustawisz go na najwyższy poziom, Wszystkie się skompilują. Jeśli ostatnio używasz wysokiego poziomu szczegółowości debugowania, prawdopodobnie wszystkie już się skompilowały. Nigdy nie potrzebowałem więcej niż trzech poziomów, ale Jonathan mówi, że użył dziewięciu. Jego metoda również może mieć poziomy szczegółowości drukowania debugowania. Mój może być łatwo rozszerzony do obsługi dowolnej liczby poziomów, a jego może obsługa dowolnej liczby poziomów. Korzystanie z mojej metody może być prostsze; wymaga tylko dwóch instrukcji, gdy są używane w Twoim kodzie (chociaż ja również używam trzeciej).

W stosunku do kosztów dodatkowym etapem testowania ich, aby zobaczyć, że będą kompilowane przed dostawą, jest to, że

  1. musisz im zaufać, aby zostali zoptymalizowani, Co Co prawda powinno się zdarzyć, jeśli masz wystarczający poziom optymalizacji.
  2. ponadto, prawdopodobnie nie będzie, jeśli wydanie skompilować z optymalizacja wyłączona dla celów testowych (co jest co prawda rzadkie); i prawie na pewno nie będzie w ogóle podczas debugowania-w ten sposób wykonując dziesiątki lub setki poleceń" if (DEBUG) " w czasie wykonywania; w ten sposób spowalniając wykonywanie (co jest moim sprzeciwem) i mniej ważne, zwiększając rozmiar pliku wykonywalnego lub dll; a tym samym czas wykonywania i kompilacji. Jonathan jednak informuje mnie, że jego metoda może być wykonana, aby w ogóle nie kompilować oświadczeń.
[[13]}gałęzie są właściwie stosunkowo dość kosztowne w nowoczesnych procesorach wstępnego pobierania. Może to nic wielkiego, jeśli Twoja aplikacja nie jest krytyczna czasowo; ale jeśli wydajność jest problemem, to tak, wystarczająco duża sprawa, że wolałbym zdecydować się na nieco szybsze wykonywanie kodu debugowania(i być może szybsze wydanie, w rzadkich przypadkach, jak zauważono).

Więc to, czego chciałem, to makro debugowania, które nie kompiluje się, jeśli nie ma być wydrukowane, ale robi, jeśli jest. Chciałem również poziomy debugowania, tak aby np. jeśli chciałem wydajność-kluczowe części kodu nie do drukowania w niektórych momentach, ale do drukowania w innych, mógłbym ustawić poziom debugowania i mieć dodatkowe wydruki debugowania kopać. Natknąłem się na sposób implementacji poziomów debugowania, które określały, czy druk był w ogóle skompilowany, czy nie. Osiągnąłem to w ten sposób:

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

Używanie makr

Aby go użyć, po prostu zrób:

DEBUGLOG_INIT("afile.log");

Aby zapisać do pliku dziennika, po prostu wykonaj:

DEBUGLOG_LOG(1, "the value is: %d", anint);

Aby zamknąć, robisz:

DEBUGLOG_CLOSE();

Chociaż obecnie nie jest to nawet konieczne, technicznie rzecz biorąc, ponieważ nic nie robi. Nadal używam teraz CLOSE, jednak na wypadek, gdybym zmienił zdanie na temat tego, jak to działa i chciał pozostawić plik otwarty między instrukcjami logowania.

Następnie, gdy chcesz włączyć drukowanie debugowania, po prostu edytuj pierwsze # define w pliku nagłówkowym, aby powiedzieć, np.

#define DEBUG 1

Aby instrukcje logowania skompilowały się do niczego, wykonaj

#define DEBUG 0

Jeśli potrzebujesz informacji z często wykonywanego fragmentu kodu (tj. o wysokim poziomie szczegółowości) możesz chcieć napisać:

 DEBUGLOG_LOG(3, "the value is: %d", anint);

Jeśli zdefiniujesz debugowanie jako 3, rejestrowanie poziomów 1, 2 i 3 skompiluje się. Jeśli ustawisz go na 2, otrzymasz poziomy logowania 1 i 2. Jeśli ustawisz ją na 1, otrzymasz tylko instrukcje logowania poziomu 1.

Jeśli chodzi o pętlę do-while, ponieważ jest to ewaluacja pojedynczej funkcji lub nic, zamiast instrukcji if, pętla nie jest potrzebna. Ok, wykastruj mnie za używanie C zamiast C++ IO (i Qt QString:: arg() jest bezpieczniejszym sposobem formatowania zmiennych również w Qt - jest to dość sprytne, ale zajmuje więcej kodu, a dokumentacja formatowania nie jest tak zorganizowana, jak może być - ale nadal znalazłem przypadki, w których jest to preferowane), ale możesz umieścić dowolny kod w .plik cpp, który chcesz. Może to być również klasa, ale wtedy będziesz musiał utworzyć instancję i nadążyć za nią, lub zrobić nową () i zapisać ją. W ten sposób po prostu upuszczasz do źródła instrukcje #include, init i opcjonalnie close, i jesteś gotowy, aby rozpocząć korzystanie z niego. To byłaby dobra Klasa, jednak, jeśli jesteś tak skłonny.

Widziałem wcześniej wiele rozwiązań, ale żadne nie odpowiadało moim kryteriom tak dobrze, jak to.

  1. można go rozszerzyć, aby zrobić tyle poziomów, ile chcesz.
  2. kompiluje się do niczego, jeśli nie drukuje.
  3. centralizuje IO w jednym łatwym do edycji miejscu.
  4. jest elastyczny, używając formatowania printf.

Dodatkowo,

  1. wymaga brak hackowania do wydruku bez argumentów (np. ERRLOG_LOG(3, "got here!");); dzięki temu korzystanie z np. Qt jest bezpieczniejsze .formatowanie arg (). Działa na MSVC, a więc prawdopodobnie gcc. Używa ## w #define s, co jest niestandardowe, jak wskazuje Leffler, ale jest szeroko wspierane. (Możesz go przekodować, aby nie używać ##, jeśli to konieczne, ale będziesz musiał użyć hacka, takiego jak on zapewnia.)

Warning: If you forget to provide the logging level argument, MSVC unhelpfully claims the identifier is not zdefiniowany.

Możesz użyć nazwy symbolu preprocesora innej niż DEBUG, ponieważ niektóre źródła również definiują ten symbol (np. progs używając ./configure poleceń do przygotowania do budowy). Wydawało mi się to naturalne, kiedy go rozwinąłem. Rozwinąłem go w aplikacji, w której DLL jest używany przez coś innego, i bardziej opłaca się wysyłać wydruki logów do pliku; ale zmiana go na vprintf() też by zadziałała.

Mam nadzieję, że oszczędzi to wielu z was smutku z powodu znalezienia najlepszego sposobu aby zrobić logowanie debugowania; lub pokazuje jeden może wolisz. Od dziesięcioleci próbuję to rozgryźć. Działa w MSVC 2012 & 2015, a więc prawdopodobnie na gcc;, a także prawdopodobnie działa na wielu innych, ale nie testowałem go na nich.

 0
Author: CodeLurker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:47

Wierzę, że ta odmiana tematu daje kategorie debugowania bez konieczności posiadania osobnej nazwy makra dla kategorii.

Użyłem tej odmiany w projekcie Arduino, gdzie przestrzeń programu jest ograniczona do 32K, a pamięć dynamiczna jest ograniczona do 2k. dodanie instrukcji debug i ciągów debugowania śledzenia szybko zużywa przestrzeń. Dlatego ważne jest, aby móc ograniczyć ślad debugowania, który jest zawarty w czasie kompilacji do niezbędnego minimum za każdym razem, gdy kod jest zbudowany.

Debugowanie.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif
Dzwonię .plik cpp
#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
 -1
Author: user358795,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-04 22:53:17