wake-up-neo.com

DEBUG-Makros in C++

Ich habe gerade ein DEBUG-Makro in C getroffen, das ich wirklich mag

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

Ich vermute, ein analoges C++ wäre: -

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

BEARBEITEN: .__ Mit "Debug-Makros" meine ich "Makros, die sich beim Ausführen eines Programms im Debug-Modus als nützlich erweisen könnten".

49
user277465

Ist der zweite Codeausschnitt analog zu dem in C?

Mehr oder weniger. Es ist leistungsfähiger, da Sie <<-getrennte Werte in das Argument einfügen können. Mit einem einzigen Argument erhalten Sie etwas, das eine variable Anzahl von Makroargumenten in C erfordern würde. Auf der anderen Seite besteht eine geringe Wahrscheinlichkeit, dass die Leute dies tun werden Missbrauchen Sie es, indem Sie das Argument mit einem Semikolon versehen. Oder sogar Fehler durch einen vergessenen Semikolon nach dem Anruf. Also würde ich dies in einen do-Block einfügen:

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

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

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

#define DEBUG(x)

dies hat den gleichen Effekt für die Optimierung von Compilern. Obwohl der Kommentar von @Tony D unten richtig ist: Dies kann einige Syntaxfehler unerkannt lassen.

Manchmal füge ich auch eine Laufzeitüberprüfung hinzu, wodurch eine Art Debug-Flag bereitgestellt wird. Wie @Tony D mich erinnert hat, ist es oft auch nützlich, dort ein Ende zu haben.

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

Manchmal möchte ich auch den Ausdruck ausdrucken:

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

In einigen Makros füge ich gerne __FILE__, __LINE__ oder __func__ ein. Dies sind jedoch häufig Assertions und keine einfachen Debug-Makros.

38
MvG

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 im Release-Modus schnellen !!) 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 einer D() umbrechen. 

Wie benutzt man: 

D(cerr << "oopsie";)

Wenn das für dich 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 (vorschlagen)using namespace std; nicht verwenden, obwohl using std::cout; using std::cerr; eine gute Idee sein könnte.

Beachten Sie, dass Sie mehr Dinge tun möchten, als nur an stderr zu drucken, wenn Sie über "Debugging" nachdenken. Machen Sie sich kreativ, und Sie können Konstrukte erstellen, die Einblick in die komplexesten Interaktionen innerhalb Ihres Programms bieten, während Sie sehr schnell zum Erstellen einer äußerst effizienten Version wechseln können, die nicht durch Debug-Instrumente belastet ist. 

In einem meiner letzten Projekte hatte ich beispielsweise einen großen Debug-Block, der mit FILE* file = fopen("debug_graph.dot"); begann und einen graphviz -kompatiblen Graphen im Punktformat ablegte, 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 geändert wird. Die Grafik wird also jedes Mal aktualisiert, wenn das Programm ausgeführt wird!

Ich mag es auch besonders, Klassen/Strukturen um Debug-only-Member und -Funktionen zu erweitern. Dies eröffnet die Möglichkeit, Funktionen und Zustände zu implementieren, die dazu dienen, Fehler aufzuspüren, und genau wie alles andere, was in Debug-Makros enthalten ist, wird dieser durch das Umschalten eines Build-Parameters entfernt. Eine riesige Routine, die bei jedem Status-Update jeden Eckpunkt sorgfältig überprüft? Kein Problem. Schlagen Sie eine D() um. Sobald Sie wissen, dass es funktioniert, entfernen Sie -DDEBUG aus dem Build-Skript, d. H. Build for Release, und es ist fertig. Es kann sofort nach der Benachrichtigung erneut für Ihre Komponententests aktiviert werden. 

Ein großes, etwas umfassendes Beispiel, um die Verwendung dieses Konzepts (vielleicht etwas übereifrig) 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 reguläre #ifdef-Bedingungen für Blöcke verwendet, da dies die Lesbarkeit etwas verbessert, da für große Blöcke die Verwendung extrem kurzer Makros eher hinderlich ist!

Der Grund, warum das N(x)-Makro vorhanden sein muss, besteht darin, anzugeben, was add sein soll, wenn Unit-Testing deaktiviert ist. 

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

Dies ist jedoch nicht möglich, da das Komma Teil der Präprozessor-Syntax ist. Wenn Sie das Komma weglassen, wird die C++ - Syntax ungültig. 

Wenn Sie zusätzlichen Code für not haben, der zum Debuggen kompiliert wird, können Sie diesen Typ eines entsprechenden inversen Debug-Makros 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, wenn Sie diszipliniert bleiben, nicht notwendigerweise böse sind.

Ich habe dieses Juwel gerade erst getroffen, nachdem ich mich über das do{} while(0)-Zeug gewundert habe, und Sie wollen wirklich diese Phantasie auch in diesen Makros! 

Ich hoffe, dass mein Beispiel einige Einblicke in einige kluge Dinge bieten kann, die zur Verbesserung Ihres C++ - Codes durchgeführt werden können. Es ist wirklich wertvoll, den Code zu instrumentieren, während Sie ihn schreiben, anstatt ihn zurück zu tun, wenn Sie nicht verstehen, was passiert. Es ist jedoch immer ein Gleichgewicht, das Sie zwischen robuster und pünktlicher Ausführung finden müssen. 

Ich denke gerne an zusätzliche Debug-Build-Integritätsprüfungen als ein anderes Tool in der Toolbox, ähnlich wie bei Komponententests. Meiner Meinung nach könnten sie sogar noch leistungsfähiger sein, denn anstatt Ihre Logik für die Vernunftsprüfung in Komponententests einzufügen und sie von der Implementierung zu isolieren, wenn sie in die Implementierung einbezogen werden und nach Belieben beschworen werden können, sind vollständige Tests nicht so notwendig weil Sie die Prüfungen einfach aktivieren und wie gewohnt ausführen können.

32
Steven Lu

Zu Frage 1] Antwort ist ja. Es wird nur die Nachricht an den Standard-Fehlerstrom ausgegeben.

Zu Frage 2] Es gibt viele. Mein Favorit ist

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

dadurch kann eine beliebige Anzahl von Variablen in die Debug-Nachricht aufgenommen werden.

9
NeonGlow

Ich benutze gerne Makros mit __LINE__, __FILE__ als Argumente, um zu zeigen, wo im Code der Ausdruck steht. Es ist nicht ungewöhnlich, denselben Variablennamen an mehreren Stellen zu drucken, so dass fprintf(stderr, "x=%d", x); nicht viel bedeutet, wenn Sie dann hinzufügen ein anderes die gleichen zehn Zeilen weiter unten. 

Ich habe auch Makros verwendet, die bestimmte Funktionen überschreiben und speichern, von wo aus sie aufgerufen wurden (z. B. Speicherzuordnungen), sodass ich später herausfinden kann, bei welcher Funktion sie durchgesickert ist. Für die Speicherzuweisung ist dies in C++ etwas schwieriger, da Sie neustart/delete verwenden und nicht einfach ersetzt werden können. Andere Ressourcen wie Sperren/Entsperren können jedoch sehr nützlich sein, um diesen Weg zu verfolgen [natürlich]. Wenn Sie einen Wrapper-Wrapper verwenden, der die Konstruktion/Zerstörung wie ein guter C++ - Programmierer verwendet, fügen Sie ihn dem Konstruktor hinzu, um Datei/Zeile zur internen Struktur hinzuzufügen, sobald Sie die Sperre erworben haben, und Sie können sehen, wo sie woanders abgelegt wird das kannst du nicht irgendwo erwerben]. 

8
Mats Petersson

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...
6
firestoke

Dies ist meine Version, die eine variadische Vorlagenfunktion print verwendet:

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 I macht aus dem debug_print eine variadische Template-Funktion, die einen 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, dass die print-Funktion die Vorschau von Visual Studio 2013 stürzt (ich habe die RC nicht getestet). Ich habe festgestellt, dass es schneller ist (unter Windows, wo die Konsolenausgabe langsam ist) als bei meiner vorherigen Lösung, bei der eine ostream-Kindklasse verwendet wurde, die operator<< überladen hat.

Sie können auch eine temporäre stringstream innerhalb von print verwenden, wenn Sie die echte Ausgabefunktion nur einmal aufrufen möchten (oder Ihre eigene typensichere printf ;-) schreiben.

4
rubenvb

… Und als Nachtrag zu allen Antworten:

Ich persönlich verwende niemals Makros wie DEBUG, um den Debug-Code vom Release-Code zu unterscheiden, stattdessen verwende ich NDEBUG, das muss definiert werden für Release-Builds, um assert()-Aufrufe zu entfernen (ja, ich benutze assert() ausgiebig). Wenn letzteres nicht definiert ist, ist es ein Debug-Build. Einfach! Es gibt also eigentlich keinen Grund, ein weiteres Debugmakro einzuführen! (und behandeln mögliche Fälle, in denen DEBUG und NDEBUG nicht definiert sind).

4
zaufi

Ich verwende den folgenden Code für die Protokollierung. Es gibt einige Vorteile:

  1. Ich kann sie zur Laufzeit ein- und ausschalten.
  2. Ich kann Anweisungen auf einer bestimmten Protokollebene kompilieren. Im Moment habe ich zum Beispiel im KIMI_PRIVATE-Makro unbedingt kompiliert, weil ich etwas im Release-Build debugge, aber da viele potenziell geheime Saucen-Sachen protokolliert werden (lol), kompiliere ich sie aus Release-Builds .

Dieses Muster hat mir über die Jahre sehr gut gedient. Anmerkung: Obwohl es eine globale logMessage-Funktion gibt, wird das Protokoll in der Regel in einem Protokoll-Thread in eine Warteschlange gestellt.

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif
3
cheez

Ich benutze folgendes Mikro,

#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif

BENUTZEN: 

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");
0