Come si crea una funzione di solo debug che accetta un elenco di argomenti variabili?Come printf()

StackOverflow https://stackoverflow.com/questions/15240

  •  08-06-2019
  •  | 
  •  

Domanda

Mi piacerebbe creare una funzione di registrazione del debug con gli stessi parametri di printf.Ma uno che può essere rimosso dal preprocessore durante le build ottimizzate.

Per esempio:

Debug_Print("Warning: value %d > 3!\n", value);

Ho esaminato le macro variadic ma quelle non sono disponibili su tutte le piattaforme. gcc li sostiene, msvc non.

È stato utile?

Soluzione

Lo faccio ancora alla vecchia maniera, definendo una macro (XTRACE, di seguito) che è correlata a una no-op o a una chiamata di funzione con un elenco di argomenti variabili.Internamente, chiama vsnprintf in modo da poter mantenere la sintassi printf:

#include <stdio.h>

void XTrace0(LPCTSTR lpszText)
{
   ::OutputDebugString(lpszText);
}

void XTrace(LPCTSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512]; // get rid of this hard-coded buffer
    nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer);
    va_end(args);
}

Quindi un tipico cambio #ifdef:

#ifdef _DEBUG
#define XTRACE XTrace
#else
#define XTRACE
#endif

Beh, questo può essere ripulito un po', ma è l'idea di base.

Altri suggerimenti

Ecco come eseguo il debug delle stampe in C++.Definisci 'dout' (debug out) in questo modo:

#ifdef DEBUG
#define dout cout
#else
#define dout 0 && cout
#endif

Nel codice utilizzo 'dout' proprio come 'cout'.

dout << "in foobar with x= " << x << " and y= " << y << '\n';

Se il preprocessore sostituisce 'dout' con '0 && cout', tieni presente che << ha una precedenza maggiore di && e la valutazione in cortocircuito di && fa sì che l'intera riga valga 0.Poiché lo 0 non viene utilizzato, il compilatore non genera alcun codice per quella riga.

Ecco qualcosa che faccio in C/C++.Prima di tutto, scrivi una funzione che utilizza il materiale varargs (vedi il collegamento nel post di Stu).Quindi fai qualcosa del genere:


 int debug_printf( const char *fmt, ... );
 #if defined( DEBUG )
  #define DEBUG_PRINTF(x) debug_printf x
 #else
   #define DEBUG_PRINTF(x)
 #endif

 DEBUG_PRINTF(( "Format string that takes %s %s\n", "any number", "of args" ));

Tutto quello che devi ricordare è usare le doppie parentesi quando chiami la funzione debug e l'intera riga verrà rimossa nel codice non DEBUG.

Un altro modo divertente per eliminare le funzioni variadiche è:

#define function sizeof

@CodingTheWheel:

C'è un piccolo problema con il tuo approccio.Considera una chiamata come

XTRACE("x=%d", x);

Funziona bene nella build di debug, ma nella build di rilascio si espanderà in:

("x=%d", x);

Che è C perfettamente legittimo e verrà compilato e solitamente eseguito senza effetti collaterali ma genera codice non necessario.L'approccio che di solito utilizzo per eliminare questo problema è:

  1. Fai in modo che la funzione XTrace restituisca un int (restituisci semplicemente 0, il valore restituito non ha importanza)

  2. Modificare #define nella clausola #else in:

    0 && XTrace
    

Ora la versione di rilascio si espanderà in:

0 && XTrace("x=%d", x);

e qualsiasi ottimizzatore decente eliminerà tutto poiché la valutazione del cortocircuito avrebbe impedito l'esecuzione di qualsiasi cosa dopo &&.

Naturalmente, proprio mentre scrivevo l'ultima frase, mi sono reso conto che forse anche il modulo originale potrebbe essere ottimizzato e, nel caso di effetti collaterali, come le chiamate di funzione passate come parametri a XTrace, potrebbe essere una soluzione migliore poiché lo farà assicurati che le versioni di debug e di rilascio si comportino allo stesso modo.

In C++ puoi utilizzare l'operatore di streaming per semplificare le cose:

#if defined _DEBUG

class Trace
{
public:
   static Trace &GetTrace () { static Trace trace; return trace; }
   Trace &operator << (int value) { /* output int */ return *this; }
   Trace &operator << (short value) { /* output short */ return *this; }
   Trace &operator << (Trace &(*function)(Trace &trace)) { return function (*this); }
   static Trace &Endl (Trace &trace) { /* write newline and flush output */ return trace; }
   // and so on
};

#define TRACE(message) Trace::GetTrace () << message << Trace::Endl

#else
#define TRACE(message)
#endif

e usarlo come:

void Function (int param1, short param2)
{
   TRACE ("param1 = " << param1 << ", param2 = " << param2);
}

Puoi quindi implementare l'output di traccia personalizzato per le classi più o meno nello stesso modo in cui lo faresti per l'output std::cout.

Ah, vsprintf() era la cosa che mi mancava.Posso usarlo per passare l'elenco degli argomenti variabili direttamente a printf():

#include <stdarg.h>
#include <stdio.h>

void DBG_PrintImpl(char * format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsprintf(buffer, format, args);
    printf("%s", buffer);
    va_end(args);
}

Quindi racchiudi il tutto in una macro.

Su quali piattaforme non sono disponibili?stdarg fa parte della libreria standard:

http://www.opengroup.org/onlinepubs/009695399/basedefs/stdarg.h.html

Qualsiasi piattaforma che non la fornisce non è un'implementazione C standard (o molto, molto vecchia).Per questi, dovrai usare varargs:

http://opengroup.org/onlinepubs/007908775/xsh/varargs.h.html

Parte del problema con questo tipo di funzionalità è che spesso richiede macro variadiche.Questi sono stati standardizzati abbastanza di recente (C99), e molti di i vecchi compilatori C non supportano lo standard, o hanno un loro lavoro speciale intorno.

Di seguito è riportata un'intestazione di debug che ho scritto che presenta diverse funzionalità interessanti:

  • Supporta la sintassi C99 e C89 per le macro di debug
  • Abilita/Disabilita l'output in base all'argomento della funzione
  • Output nel descrittore di file (file io)

Nota:Per qualche motivo ho avuto alcuni lievi problemi di formattazione del codice.

#ifndef _DEBUG_H_
#define _DEBUG_H_
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "stdarg.h"
#include "stdio.h"

#define ENABLE 1
#define DISABLE 0

extern FILE* debug_fd;

int debug_file_init(char *file);
int debug_file_close(void);

#if HAVE_C99
#define PRINT(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, format, ##__VA_ARGS__); \
} \
else { \
fprintf(stdout, format, ##__VA_ARGS__); \
} \
}
#else
void PRINT(int enable, char *fmt, ...);
#endif

#if _DEBUG
#if HAVE_C99
#define DEBUG(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
else { \
fprintf(stderr, "%s : %d " format, __FILE__, __LINE__, ##__VA_ARGS__); \
} \
}

#define DEBUGPRINT(x, format, ...) \
if ( x ) { \
if ( debug_fd != NULL ) { \
fprintf(debug_fd, format, ##__VA_ARGS__); \
} \
else { \
fprintf(stderr, format, ##__VA_ARGS__); \
} \
}
#else /* HAVE_C99 */

void DEBUG(int enable, char *fmt, ...);
void DEBUGPRINT(int enable, char *fmt, ...);

#endif /* HAVE_C99 */
#else /* _DEBUG */
#define DEBUG(x, format, ...)
#define DEBUGPRINT(x, format, ...)
#endif /* _DEBUG */

#endif /* _DEBUG_H_ */

Dai un'occhiata a questo thread:

Dovrebbe rispondere alla tua domanda.

Avendo riscontrato il problema oggi, la mia soluzione è la seguente macro:

    static TCHAR __DEBUG_BUF[1024]
    #define DLog(fmt, ...)  swprintf(__DEBUG_BUF, fmt, ##__VA_ARGS__); OutputDebugString(__DEBUG_BUF) 

Puoi quindi chiamare la funzione in questo modo:

    int value = 42;
    DLog(L"The answer is: %d\n", value);

Questo è quello che uso:

inline void DPRINTF(int level, char *format, ...)
{
#    ifdef _DEBUG_LOG
        va_list args;
        va_start(args, format);
        if(debugPrint & level) {
                vfprintf(stdout, format, args);
        }
        va_end(args);
#    endif /* _DEBUG_LOG */
}

che non costa assolutamente nulla in fase di esecuzione quando il flag _DEBUG_LOG è disattivato.

Questa è una versione TCHAR della risposta dell'utente, quindi funzionerà come ASCII (normale) o modalità Unicode (più o meno).

#define DEBUG_OUT( fmt, ...) DEBUG_OUT_TCHAR(       \
            TEXT(##fmt), ##__VA_ARGS__ )
#define DEBUG_OUT_TCHAR( fmt, ...)                  \
            Trace( TEXT("[DEBUG]") #fmt,            \
            ##__VA_ARGS__ )
void Trace(LPCTSTR format, ...)
{
    LPTSTR OutputBuf;
    OutputBuf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT,   \
            (size_t)(4096 * sizeof(TCHAR)));
    va_list args;
    va_start(args, format);
    int nBuf;
    _vstprintf_s(OutputBuf, 4095, format, args);
    ::OutputDebugString(OutputBuf);
    va_end(args);
    LocalFree(OutputBuf); // tyvm @sam shaw
}

Dico "più o meno", perché non convertirà automaticamente gli argomenti della stringa ASCII in WCHAR, ma dovrebbe farti uscire dalla maggior parte dei problemi Unicode senza doversi preoccupare di racchiudere la stringa di formato in TEXT() o di precederla con L .

In gran parte derivato da MSDN:Recupero del codice dell'ultimo errore

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top