2008-08-18 40 views
35

Chciałbym wykonać funkcję rejestrowania debugowania z tymi samymi parametrami, co printf. Ale taki, który może zostać usunięty przez preprocesor podczas optymalizacji kompilacji.Jak utworzyć funkcję debugowania, która pobiera zmienną listę argumentów? Podobnie jak printf()

Na przykład:

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

Szukałem w zmiennej liczbie argumentów makr, ale te nie są dostępne na wszystkich platformach. gcc obsługuje je, msvc nie.

+0

Stu MSVC obsługuje funkcję zmiennej liczbie argumentów, że nie obsługuje zmiennej liczbie argumentów makra. Edycja: My bad: Obsługa makr variadic została wprowadzona w Visual C++ 2005. – hyperlogic

+0

Zobacz także [C '# define' makro do drukowania debugowania] (https://stackoverflow.com/questions/1644868/c-define-macro- -debug-printing). Zwróćcie uwagę w szczególności, że ogólnie najlepiej jest upewnić się, że kompilator kompiluje (ale optymalizuje) kod z makra debugowania, tak że kod jest zawsze sprawdzany, a zatem zawsze poprawny. W przeciwnym razie bit-rot można ustawić i po ponownym uaktywnieniu makra debugowania dekadę później okaże się, że nie kompiluje się. –

Odpowiedz

22

Wciąż robię to w starym stylu, definiując makro (XTRACE, poniżej), które jest skorelowane z funkcją no-op lub funkcją z zmienną listą argumentów. Wewnętrznie, zadzwoń vsnprintf dzięki czemu można zachować składni 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); 
} 

Następnie typowego przełącznika #ifdef:

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

dobrze, że mogą być czyszczone trochę ale to podstawowa idea.

+0

Doskonała odpowiedź, ale powinieneś był użyć ** _ vsntprintf **, aby faktycznie uczynić ją kompatybilną z Unicode. Dodałem własną wersję, ponieważ musiałem przedłożyć ciąg znaków (jak [DEBUG]). http://stackoverflow.com/a/39186784/912236 – Orwellophile

+0

'_vsnprintf()' jest specyficzne dla konkretnych implementacji (zgodnych z Microsoftem) i przestarzałe. 'vsnprintf()' jest standardem. – Peter

1

Na jakich platformach nie są dostępne? stdarg jest częścią biblioteki standardowej:

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

Każda platforma nie zapewniając jej nie jest standardowym wdrożenie C (lub bardzo, bardzo stary). Dla tych, będziesz musiał użyć varargs:

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

+0

http://msdn.microsoft.com/en-us/library/kb57fad8(VS.71).aspx - Dla przypomnienia, ten artykuł pokazuje nawet, jak używać oldarkowych varargs jako bonusu. – Stu

+0

Wygląda na to, że obsługa makr variadic została wprowadzona w Visual C++ 2005. – hyperlogic

11

Oto coś, co mogę zrobić w C/C++. Po pierwsze, piszesz funkcję, która używa rzeczy varargs (zobacz link w postu Stu). Następnie zrobić coś takiego:


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

Wszystko trzeba pamiętać, jest użycie podwójnych nawiasów przy wywołaniu funkcji debugowania, a cała linia będzie uzyskać usunięte w kodzie non-debug.

2

Ah, vsprintf() było tym, czego mi brakowało. Mogę tego użyć, aby przekazać listę zmiennych bezpośrednio do 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); 
} 

Następnie zawiń całość w makro.

4

Innym ciekawym sposobem skrótową z funkcji o zmiennej liczbie argumentów jest:

#define function sizeof 
2

W C++ można użyć operatora strumieniowego uprościć rzeczy:

#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 

i używać go lubię:

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

Następnie można zaimplementować niestandardowe wyniki śledzenia dla klas w taki sam sposób, jak w przypadku wyprowadzania do std::cout.

1

Częścią problemu z tego rodzaju funkcjonalnością jest to, że często wymaga on makr variadycznych .Zostały one ustandaryzowane dość niedawno (C99), a wiele starych kompilatorów C nie obsługuje standardu lub ma swoje własne specjalne dzieło wokół.

Poniżej znajduje się nagłówek debugowania napisałem, że ma kilka ciekawych funkcji:

  • Obsługuje C99 i C89 składnia makr debugowania
  • Włącz/Wyłącz wyjście na podstawie argumentu funkcji
  • wyjściowe do pliku deskryptora (file io)

Uwaga: Z jakiegoś powodu miałem niewielkie problemy z formatowaniem kodu.

#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_ */ 
+0

Wydaje się dziwne, aby użyć '' debug_fd' 'do przechowywania wskaźnika pliku, a nie deskryptora pliku; bardziej konwencjonalne byłoby użycie '' debug_fp'. –

+0

## __ VA_ARGS__ jest niesamowity! o tym nie wiedziałem. Dziękuję Ci! –

3

@CodingTheWheel:

Jest jeden mały problem ze swoim podejściu. Rozważyć połączenie takie jak

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

To działa dobrze w budowie debugowania, ale w wydaniu budować będzie rozszerzać się:

("x=%d", x); 

Który jest w pełni uzasadnione C i skompilować i zwykle uruchamiane bez strony -efects, ale generuje niepotrzebny kod. Podejście Zwykle używam, aby wyeliminować ten problem jest:

  1. Bądź funkcja xtrace powrócić int (po prostu zwraca 0, zwracana wartość nie ma znaczenia)

  2. Zmienić #define w # inny klauzula:

    0 && XTrace 
    

teraz wersja release wzrośnie do:

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

i każdy przyzwoity optymalizator wyrzuci całość, ponieważ ewaluacja zwarciowa uniemożliwiłaby wykonanie wszystkiego po wykonaniu & &.

Oczywiście, tak jak napisałem to ostatnie zdanie, zdałem sobie sprawę, że być może oryginalna forma może być również zoptymalizowana, a w przypadku efektów ubocznych, takich jak wywoływanie funkcji jako parametrów do XTrace, może to być lepsze rozwiązanie, ponieważ upewni się, że wersje debugowania i wydania będą zachowywać się tak samo.

20

Tak robię debugowanie wydruków w C++. Zdefiniuj 'DOUT' (debug out) tak:

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

w kodzie używam 'DOUT' podobnie jak 'cout'.

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

jeśli zastępuje preprocesor DOUT „” notatki „0 & & cout” że < < ma wyższy priorytet niż & & i oceny zwarciowej & & sprawia, że ​​cała linia ocenić 0.Ponieważ 0 nie jest używane, kompilator nie generuje żadnego kodu dla tej linii.

+0

Uważam, że następująca modyfikacja jest przydatna: '#define dout cout << __FILE__ <<" ("<< __LINE__ <<") DEBUG: "' – cledoux

+0

W tym przypadku nie będzie można użyć na przykład: 'dout << setw (10) << "test"; ' – sohaibafifi

+2

Generuje ostrzeżenia (gcc-Wall), gdy debugowanie nie jest włączone: " ostrzeżenie: instrukcja nie ma żadnego efektu [-Wunused-value] 0 && std :: cout << " test "; –

0

Po natknąć się dzisiaj problem, moje rozwiązanie jest następujące makro:

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

Następnie można wywołać funkcję tak:

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

To co mam użyć:

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 */ 
} 

który nie kosztuje absolutnie nic w czasie wykonywania, gdy flaga _DEBUG_LOG jest wyłączona.

+1

Spowoduje to wygenerowanie ostrzeżeń o nieużywanych parametrach funkcji –

0

To jest wersja TCHAR odpowiedzi użytkownika, więc będzie działać jako ASCII (normalna) lub w trybie Unicode (mniej więcej).

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

mówię, „mniej więcej”, ponieważ nie będzie automatycznie konwertować argumenty ciąg znaków ASCII do WCHAR, ale powinno cię z większości zadrapania Unicode bez konieczności martwienia się o zawijania ciąg formatu, w TEKST() i poprzedzający go L.

pochodzi głównie z MSDN: Retrieving the Last-Error Code

+1

hej, zapomnisz użyć api localfree i spowoduje to wyciek pamięci.Nawet jeśli wyzwolicie to, nie jest dobrym pomysłem użycie sterty dla tego przypadku. –

+0

@SamShaw dobrze zauważony, dołączyłem także powiadomienie znoszące wszelką dalszą odpowiedzialność, wskazując na jego pochodzenie MSDN. Sugerujesz, że sterty są złe, ponieważ moglibyśmy rejestrować niepowodzenie przydziału()? Nie mogę się z tym kłócić, ponieważ kiedy pisałem powyższy kod, zdałem sobie sprawę, że nasz dotychczasowy Logger pobierał informacje z rejestru i poszło trochę szalenie ... * postnote: moje królestwo za makro VS, które da mi wersja TCHAR dowolnej funkcji * – Orwellophile