DEBUG-Makros in C++

Lesezeit: 15 Minuten

Ich bin gerade auf ein DEBUG-Makro in C gestoßen, das mir wirklich gefällt

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

Ich vermute, ein C ++ – Analogon wäre: –

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. Ist das zweite Code-Snippet analog zu dem in C?
  2. Haben Sie Lieblings-C++-Debug-Makros?

BEARBEITEN: Mit “Debug-Makros” meine ich “Makros, die beim Ausführen eines Programms im Debug-Modus nützlich sein könnten”.

  • Bitte lesen Sie die FAQ zu Ihrer zweiten Frage…

    – Oliver Charlesworth

    10. Januar 2013 um 4:58 Uhr

  • Irgendwie unabhängig, aber Dies vielleicht gut zu lesen.

    – Karthik T

    10. Januar 2013 um 5:02 Uhr

  • @OliCharlesworth Mir ist klar, dass meine zweite Frage subjektiv sein könnte – ich frage jedoch nicht nach einer Meinung zu etwas Bestimmtem, ich frage nach Codebeispielen, die häufig von Programmierern auf SO verwendet werden. Wenn die Besucher jedoch immer noch das Gefühl haben, dass die Frage nicht für SO geeignet ist, werde ich dafür sorgen, dass ich sie herausbearbeite.

    Benutzer277465

    10. Januar 2013 um 5:09 Uhr

  • @Pubby Entschuldigung, unklar gewesen zu sein – ich meinte “Makros, die beim Ausführen eines Programms im Debug-Modus nützlich sein könnten”

    Benutzer277465

    10. Januar 2013 um 5:12 Uhr

  • sieht für mich perfekt aus, wird funktionieren

    – Kinjal Patel

    10. Januar 2013 um 5:13 Uhr

Benutzeravatar von MvG
MvG

Ist das zweite Code-Snippet analog zu dem in C?

Mehr oder weniger. Es ist mächtiger, als Sie einschließen können <<-getrennte Werte im Argument, sodass Sie mit einem einzigen Argument etwas erhalten, das eine variable Anzahl von Makroargumenten in C erfordern würde. Andererseits besteht eine geringe Chance, dass Leute es missbrauchen, indem sie ein Semikolon in das Argument einfügen. Oder es kommt sogar zu Fehlern durch ein vergessenes Semikolon nach dem Aufruf. Also würde ich dies in einen Do-Block aufnehmen:

#define DEBUG(x) do { std::cerr << x; } while (0)

Haben Sie Lieblings-C++-Debug-Makros?

Ich mag das oben genannte und benutze es ziemlich oft. Mein No-Op liest normalerweise nur

#define DEBUG(x)

was den gleichen Effekt für die Optimierung von Compilern hat. Obwohl der Kommentar von @Tony D unten richtig ist: Dadurch können einige Syntaxfehler unentdeckt bleiben.

Manchmal füge ich auch eine Laufzeitprüfung hinzu und stelle so eine Art Debug-Flag bereit. Wie @Tony D mich daran erinnerte, ist es oft auch nützlich, ein Endl darin zu haben.

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

Manchmal möchte ich auch den Ausdruck drucken:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

In einige Makros binde ich gerne ein __FILE__, __LINE__ oder __func__aber das sind häufiger Behauptungen und keine einfachen Debug-Makros.

  • “Mein No-Op liest normalerweise nur #define DEBUG(x)“… a do-while (false) Die Ersetzung führt tendenziell zu einem Fehler, wenn das Semikolon des DEBUG-Aufrufs fehlt, anstatt die nächste Anweisung an die Stelle des DEBUG zu schieben. ZB “if (expr) DEBUG”, nächste Zeile: “++i;”, Sie würden “if (expr) ++i;” erhalten. Kleiner Grund, Do-while zu bevorzugen. Viel Zeit investieren “<< '\n'” in das Makro ist auch am besten.

    – Toni Delroy

    3. Juni 2013 um 4:54 Uhr

  • @TonyD: Gute Punkte, aktualisierte meine Antwort. Obwohl std::endl überlegen ist '\n' da es auch den Stream spült.

    – MVG

    3. Juni 2013 um 6:17 Uhr

  • Im Falle des std::cerres ist normalerweise zeilengepuffert, wird also trotzdem gelöscht und verwendet std::endl ist billig überflüssig, aber – ärgere dich – was mich stört, ist, dass Leute es benutzen std::endl in Streaming-Betreibern, die verwendet werden könnten std::coutein Dateistream usw. – das schraubt stark an der Pufferung und kann zu einer dramatisch schlechteren Leistung führen.

    – Toni Delroy

    3. Juni 2013 um 9:47 Uhr


  • Das No-Op, das von verwendet wird assert ist ((void)0)

    – Jack Wasey

    25. Mai 2018 um 14:35 Uhr

Benutzeravatar von Steven Lu
Steven Lu

Hier ist mein Favorit

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

Es ist super praktisch und sorgt für sauberen (und vor allem schnellen im Release-Modus!!) Code.

Viele #ifdef DEBUG_BUILD Blöcke überall (um Debug-bezogene Codeblöcke herauszufiltern) ist ziemlich hässlich, aber nicht so schlimm, wenn Sie ein paar Zeilen mit a umbrechen D().

Wie benutzt man:

D(cerr << "oopsie";)

Wenn dir das immer noch zu hässlich/komisch/lang ist,

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(Ich schlage vor, nicht zu verwenden using namespace std; obwohl vielleicht using std::cout; using std::cerr; könnte eine gute Idee sein)

Beachten Sie, dass Sie dies tun möchten mehr Dinge als nur auf stderr drucken, wenn Sie über “Debuggen” nachdenken. Werden Sie kreativ, und Sie können Konstrukte erstellen, die Einblick in die komplexesten Interaktionen innerhalb Ihres Programms bieten, während Sie gleichzeitig sehr schnell zum Erstellen einer supereffizienten Version wechseln können, die nicht durch Debug-Instrumentierung belastet ist.

Zum Beispiel hatte ich in einem meiner letzten Projekte einen riesigen Nur-Debug-Block, der mit begann FILE* file = fopen("debug_graph.dot"); und fuhr fort, a auszuladen graphviz kompatibles Diagramm im Punktformat, um große Bäume in meinen Datenstrukturen zu visualisieren. Noch cooler ist, dass der OS X-Graphviz-Client die Datei automatisch von der Festplatte liest, wenn sie sich ändert, sodass die Grafik immer aktualisiert wird, wenn das Programm ausgeführt wird!

Ich “erweitere” auch besonders gerne Klassen/Strukturen mit Debug-only Membern und Funktionen. Dies eröffnet die Möglichkeit, Funktionen und Status zu implementieren, die Ihnen beim Aufspüren von Fehlern helfen und genau wie alles andere, was in Debug-Makros verpackt ist, durch Umschalten eines Build-Parameters entfernt werden. Eine riesige Routine, die bei jeder Zustandsaktualisierung jeden Grenzfall akribisch überprüft? Kein Problem. Schlag ein D() um es herum. Sobald Sie sehen, dass es funktioniert, entfernen Sie es -DDEBUG aus dem Build-Skript, dh Build für die Veröffentlichung, und es ist weg, bereit, jederzeit für Ihre Komponententests oder was auch immer wieder aktiviert zu werden.

Ein großes, etwas vollständiges Beispiel, um die (vielleicht etwas übereifrige) Verwendung dieses Konzepts zu veranschaulichen:

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

Beachten Sie, dass ich für große Codeblöcke nur den regulären Block verwende #ifdef Bedingungen, weil das die Lesbarkeit etwas verbessert, da bei großen Blöcken die Verwendung extrem kurzer Makros eher hinderlich ist!

Der Grund, warum die N(x) Makro muss vorhanden sein, um anzugeben, was zu tun ist hinzufügen wenn Unit-Tests sind deaktiviert.

In diesem Teil:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

Es wäre schön, wenn wir so etwas sagen könnten

GraphNode(uint64_t i, U(Component* c,) int first_edge):

Aber das können wir nicht, weil das Komma Teil der Präprozessorsyntax ist. Das Weglassen des Kommas führt zu einer ungültigen C++-Syntax.

Wenn Sie einen zusätzlichen Code für wann hätten nicht Beim Kompilieren für das Debuggen könnten Sie diese Art von entsprechendem Inverse-Debug-Makro verwenden.

Nun, dieser Code ist vielleicht kein Beispiel für „wirklich guten Code“, aber er veranschaulicht einige der Dinge, die Sie mit einer cleveren Anwendung von Makros erreichen können, die Sie jedoch nicht erreichen können, wenn Sie diszipliniert bleiben Notwendig teuflisch.

Ich bin gerade auf dieses Juwel gestoßen, nachdem ich mich über das gewundert hatte do{} while(0) Sachen, und Sie wollen wirklich all diese Extravaganz auch in diesen Makros!

Hoffentlich kann mein Beispiel einen Einblick in zumindest einige der cleveren Dinge geben, die getan werden können, um Ihren C++-Code zu verbessern. Es ist wirklich wertvoll, Code zu instrumentieren, während Sie ihn schreiben, anstatt ihn erneut zu schreiben, wenn Sie nicht verstehen, was passiert. Aber es ist immer ein Gleichgewicht, das Sie finden müssen, um es robust zu machen und es pünktlich zu erledigen.

Ich stelle mir zusätzliche Plausibilitätsprüfungen für Debug-Builds gerne als ein anderes Tool in der Toolbox vor, ähnlich wie Unit-Tests. Meiner Meinung nach könnten sie noch leistungsfähiger sein, denn anstatt Ihre Sanity-Check-Logik in Unit-Tests zu stecken und sie von der Implementierung zu isolieren, sind vollständige Tests nicht so notwendig, wenn sie in der Implementierung enthalten sind und nach Belieben heraufbeschworen werden können weil Sie zur Not einfach die Überprüfungen aktivieren und die Dinge wie gewohnt ausführen können.

  • Wow, ich habe diesen Makroparameter noch nie gekannt (x in D(x)) kann auch essen << und "". Wie weit kann es interpretiert werden? Ich habe gehört, dass der Parameter des Makros kein Zeichen enthalten darf ( oder ). Könnten Sie bitte einige Regeln/Einschränkungen/Links dazu bereitstellen? Es ist schwer, solche Regeln in Büchern zu finden. Dank.

    – cppAnfänger

    21. Januar 2019 um 6:36 Uhr


  • AFAIK, die Art und Weise, wie ein Makro funktioniert, ersetzt die Zeichenfolge, bis die Syntax dies verhindert. Da die Klammern an der Ersetzung beteiligt sind, könnten Sie beispielsweise keine nicht übereinstimmenden Klammern einbetten. Und die Kommas sind auch beteiligt (bei der Trennung der Makro-Argumente!). Aber alles andere ist Freiwild. Setzen Sie die Tricks sparsam und verantwortungsvoll ein…

    – Steven Lu

    21. Januar 2019 um 10:28 Uhr


  • Verwenden Sie niemals leere Makros wie # define D(x), sie sind sehr gefährlich! Wie in den Kommentaren zur akzeptierten Antwort (die ebenfalls unter diesem Problem leidet) darauf hingewiesen wurde, tritt ein unerwartetes Verhalten auf, wenn Sie fälschlicherweise das Semikolon danach vergessen if (x) D(x) i++; wo i++ wird nur ausgeführt, wenn x ist ungleich Null, während es jedes Mal wie ausgeführt aussieht. Wenn Sie es so formulieren # define D(x) do { } while(0) Es wird immer noch nahtlos vom Compiler entfernt, führt jedoch zu einem Kompilierungsfehler im Falle eines fehlenden Semikolons.

    – Marcin Tarsier

    31. Oktober 2019 um 13:08 Uhr

  • Du hast recht @MarcinTarsier. Ich werde es meinen Codelisten hinzufügen, um sie sicherer zu machen. Schade, dass es jetzt etwas hässlicher ist.

    – Steven Lu

    27. Januar 2020 um 21:14 Uhr


  • Dies funktioniert nicht in einer Strukturdefinition. struct { D(int proof_of_initialization;) … }. Nach mehreren Versuchen mit Debug ein- und ausschalten und dem Verschieben des Semikolons habe ich mich schließlich für die einfachen und gefährlichen #define D(x) x und #define D(x) entschieden. Dies hat wie erwähnt seine Probleme, aber auf der positiven Seite funktioniert es tatsächlich.

    – Samuel Danielson

    20. September 2021 um 13:33 Uhr

Zu Frage 1]Antwort ist ja. Die Nachricht wird einfach in den Standardfehlerstrom gedruckt.

Zu Frage 2]Es gibt viele. Mein Favorit ist

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

Dies ermöglicht es, eine beliebige Anzahl von Variablen in die Debug-Nachricht aufzunehmen.

  • Ist es möglich, ein Zeilenumbruchzeichen (\n) im Makro selbst?

    – Saurabh Shrivastava

    30. Juli 2017 um 13:53 Uhr

Benutzeravatar von Mats Petersson
Matt Petersson

Ich arbeite gerne mit Makros __LINE__, __FILE__ als Argumente zu zeigen wo im Code, aus dem der Ausdruck stammt – es ist nicht ungewöhnlich, denselben Variablennamen an mehreren Stellen auszugeben, also fprintf(stderr, "x=%d", x); nicht viel, wenn Sie dann zehn Zeilen weiter unten noch eine hinzufügen.

Ich habe auch Makros verwendet, die bestimmte Funktionen außer Kraft setzen und speichern, von wo sie aufgerufen wurden (z. B. Speicherzuweisungen), damit ich später herausfinden kann, welche davon durchgesickert ist. Für die Speicherzuweisung ist das in C++ etwas schwieriger, da Sie dazu neigen, new/delete zu verwenden, und sie nicht einfach ersetzt werden können, aber andere Ressourcen wie Sperren/Entsperren-Operationen können sehr nützlich sein, um auf diese Weise nachzuverfolgen [of course, if you have a locking wrapper that uses construction/destruction like a good C++ programmer, you’d add it to the constructor to add file/line to the internal structure once you have acquired the lock, and you can see where it’s held elsewhere when the you can’t acquire it somewhere].

Benutzeravatar von firestoke
Brandherd

Dies ist das Protokollmakro, das ich derzeit verwende:

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

Verwendungszweck:

log(">>> test...");

Ausgabe:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

  • Beobachtung: Wie aktuell gezeigt, haben Sie das Debugging immer aktiviert. Sie begrenzen auch die Ausgabe auf 100 Zeichen (was nicht sehr lang ist), aber Sie stellen nicht sicher, dass kein Pufferüberlauf durch Verwendung von erfolgt snprintf() Anstatt von sprintf(). Das ist ein gefährliches Leben, nicht wahr? Sollte Ihre Protokollierung nicht zu gehen std::cerr oder std::clog? Was ist neu und deutlich anders an dieser Antwort im Vergleich zu anderen?

    – Jonathan Leffler

    1. Juli 2015 um 4:49 Uhr

Benutzeravatar von zaufi
zaufi

… und als Nachtrag zu allen Antworten:

Persönlich verwende ich nie Makros wie DEBUG um das Debuggen vom Freigabecode zu unterscheiden, verwende ich stattdessen NDEBUG welches ist müssen definiert werden für Release-Builds zu eliminieren assert() Anrufe (ja, ich benutze assert() ausführlich). Und wenn letzteres nicht definiert ist, dann ist es ein Debug-Build. Einfach! Es gibt also eigentlich keinen Grund, noch ein Debug-Makro einzuführen! (und behandeln Sie mögliche Fälle, wenn DEBUG und NDEBUG beide sind nicht definiert).

  • Beobachtung: Wie aktuell gezeigt, haben Sie das Debugging immer aktiviert. Sie begrenzen auch die Ausgabe auf 100 Zeichen (was nicht sehr lang ist), aber Sie stellen nicht sicher, dass kein Pufferüberlauf durch Verwendung von erfolgt snprintf() Anstatt von sprintf(). Das ist ein gefährliches Leben, nicht wahr? Sollte Ihre Protokollierung nicht zu gehen std::cerr oder std::clog? Was ist neu und deutlich anders an dieser Antwort im Vergleich zu anderen?

    – Jonathan Leffler

    1. Juli 2015 um 4:49 Uhr

Benutzeravatar von rubenvb
rubenvb

Dies ist meine Version, die eine Variadic-Vorlage verwendet print Funktion:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

Die Version die ich mache debug_print eine variadische Vorlagenfunktion, die ein Debug-Level akzeptiert, mit dem ich auswählen kann, welche Art von Ausgabe ich zur Laufzeit ausgeben möchte:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

Beachten Sie das print Funktion stürzt Visual Studio 2013 Preview ab (ich habe den RC nicht getestet). Ich habe festgestellt, dass es schneller ist (unter Windows, wo die Konsolenausgabe langsam ist) als meine vorherige Lösung, die eine verwendet hat ostream untergeordnete Klasse, die überladen ist operator<<.

Sie können auch ein Provisorium verwenden stringstream Innerhalb print wenn Sie die eigentliche Ausgabefunktion nur einmal aufrufen möchten (oder Ihre eigene typesafe printf ;-))

1417760cookie-checkDEBUG-Makros in C++

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy