Como você cria uma função somente de depuração que usa uma lista de argumentos variáveis?Como printf()

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

  •  08-06-2019
  •  | 
  •  

Pergunta

Eu gostaria de criar uma função de registro de depuração com os mesmos parâmetros que printf.Mas aquele que pode ser removido pelo pré-processador durante compilações otimizadas.

Por exemplo:

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

Examinei macros variadas, mas elas não estão disponíveis em todas as plataformas. gcc os apoia, msvc não.

Foi útil?

Solução

Eu ainda faço isso da maneira antiga, definindo uma macro (XTRACE, abaixo) que se correlaciona com um ambiente autônomo ou uma chamada de função com uma lista de argumentos variáveis.Internamente, chame vsnprintf para manter a sintaxe do 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);
}

Em seguida, uma opção #ifdef típica:

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

Bem, isso pode ser um pouco limpo, mas é a ideia básica.

Outras dicas

É assim que depuro impressões em C++.Defina 'dout' (depuração) assim:

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

No código eu uso 'dout' assim como 'cout'.

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

Se o pré-processador substituir 'dout' por '0 && cout', observe que << tem precedência maior que && e a avaliação de curto-circuito de && faz com que toda a linha seja avaliada como 0.Como o 0 não é usado, o compilador não gera nenhum código para essa linha.

Aqui está algo que faço em C/C++.Primeiro, você escreve uma função que usa o material varargs (veja o link na postagem de Stu).Então faça algo assim:


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

Tudo que você precisa lembrar é usar parênteses duplos ao chamar a função de depuração, e toda a linha será removida no código não-DEBUG.

Outra maneira divertida de delinear funções variadas é:

#define function sizeof

@CodingTheWheel:

Há um pequeno problema com sua abordagem.Considere uma chamada como

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

Isso funciona bem na compilação de depuração, mas na compilação de lançamento será expandido para:

("x=%d", x);

Que é C perfeitamente legítimo e será compilado e geralmente executado sem efeitos colaterais, mas gera código desnecessário.A abordagem que costumo usar para eliminar esse problema é:

  1. Faça a função XTrace retornar um int (apenas retorne 0, o valor de retorno não importa)

  2. Altere o #define na cláusula #else para:

    0 && XTrace
    

Agora a versão de lançamento será expandida para:

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

e qualquer otimizador decente jogará tudo fora, já que a avaliação de curto-circuito teria impedido que qualquer coisa após o && fosse executado.

Claro, assim como escrevi a última frase, percebi que talvez o formato original também possa ser otimizado e no caso de efeitos colaterais, como chamadas de função passadas como parâmetros para o XTrace, pode ser uma solução melhor, pois irá certifique-se de que as versões de depuração e lançamento se comportarão da mesma forma.

Em C++ você pode usar o operador de streaming para simplificar as coisas:

#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 use-o como:

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

Você pode então implementar a saída de rastreio personalizada para classes da mesma forma que faria para a saída para std::cout.

Ah, vsprintf() era o que estava faltando.Posso usar isso para passar a lista de argumentos variáveis ​​diretamente para 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);
}

Em seguida, envolva tudo em uma macro.

Em quais plataformas eles não estão disponíveis?stdarg faz parte da biblioteca padrão:

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

Qualquer plataforma que não forneça isso não é uma implementação C padrão (ou é muito, muito antiga).Para esses, você terá que usar varargs:

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

Parte do problema com esse tipo de funcionalidade é que muitas vezes requer macros variúdicos.Estes foram padronizados razoavelmente recentemente (C99), e muitos compiladores C antigos não suportam o padrão ou têm seu próprio trabalho especial.

Abaixo está um cabeçalho de depuração que escrevi que possui vários recursos interessantes:

  • Suporta sintaxe C99 e C89 para macros de depuração
  • Ativar/desativar saída com base no argumento da função
  • Saída para descritor de arquivo (arquivo io)

Observação:Por algum motivo, tive alguns pequenos problemas de formatação de código.

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

Dê uma olhada neste tópico:

Deve responder à sua pergunta.

Tendo me deparado com o problema hoje, minha solução é a seguinte macro:

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

Você pode então chamar a função assim:

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

Isto é o que eu 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 */
}

que não custa absolutamente nada em tempo de execução quando o sinalizador _DEBUG_LOG está desativado.

Esta é uma versão TCHAR da resposta do usuário, portanto funcionará como ASCII (normal) ou modo Unicode (mais ou menos).

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

Eu digo, "mais ou menos", porque ele não converterá automaticamente argumentos de string ASCII em WCHAR, mas deve livrá-lo da maioria dos problemas de Unicode sem ter que se preocupar em agrupar a string de formato em TEXT() ou precedê-la com L .

Em grande parte derivado de MSDN:Recuperando o código do último erro

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top