c - xlprevious - vba find string




#define macro para impressão de depuração em C? (8)

Tentando criar uma macro que pode ser usada para mensagens de depuração de impressão quando o DEBUG é definido, como o seguinte pseudo-código:

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

Como isso é feito com uma macro?


Se você usar um compilador C99 ou posterior

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

Ele assume que você está usando o C99 (a notação da lista de argumentos variáveis ​​não é suportada em versões anteriores). O idioma do { ... } while (0) garante que o código funcione como uma instrução (chamada de função). O uso incondicional do código garante que o compilador sempre verifique se seu código de depuração é válido - mas o otimizador removerá o código quando o DEBUG for 0.

Se você quiser trabalhar com #ifdef DEBUG, altere a condição de teste:

#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif

E então use DEBUG_TEST onde eu usei o DEBUG.

Se você insiste em um literal de string para a string de formato (provavelmente uma boa ideia de qualquer maneira), você também pode introduzir coisas como __FILE__ , __LINE__ e __func__ na saída, o que pode melhorar o diagnóstico:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

Isso depende da concatenação de strings para criar uma string de formato maior que o programador escreve.

Se você usa um compilador C89

Se você está preso com o C89 e nenhuma extensão útil do compilador, então não há uma maneira particularmente limpa de lidar com isso. A técnica que eu costumava usar era:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

E então, no código, escreva:

TRACE(("message %d\n", var));

Os parênteses duplos são cruciais - e é por isso que você tem a notação engraçada na expansão macro. Como antes, o compilador sempre verifica o código quanto à validade sintática (o que é bom), mas o otimizador só invoca a função de impressão se a macro DEBUG for avaliada como diferente de zero.

Isso requer uma função de suporte - dbg_printf () no exemplo - para manipular coisas como 'stderr'. Requer que você saiba como escrever funções varargs, mas isso não é difícil:

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

void dbg_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

Você também pode usar essa técnica no C99, é claro, mas a técnica __VA_ARGS__ é mais __VA_ARGS__ porque usa a notação de função regular, não a hack de parênteses duplos.

Por que é crucial que o compilador sempre veja o código de depuração?

[ Rehashing comentários feitos para outra resposta. ]

Uma ideia central por trás das implementações C99 e C89 acima é que o próprio compilador sempre vê as instruções do tipo printf de depuração. Isso é importante para código de código de longo prazo que durará uma década ou duas.

Suponha que um trecho de código tenha sido praticamente inativo (estável) por vários anos, mas agora precisa ser alterado. Você reativa o rastreamento de depuração - mas é frustrante ter que depurar o código de depuração (rastreamento) porque ele se refere a variáveis ​​que foram renomeadas ou digitadas novamente, durante os anos de manutenção estável. Se o compilador (pósprocessador) sempre visualizar a declaração de impressão, isso garante que quaisquer alterações em volta não invalidaram o diagnóstico. Se o compilador não vir a declaração impressa, não poderá protegê-lo contra sua própria falta de cuidado (ou a falta de cuidado de seus colegas ou colaboradores). Veja ' A Prática da Programação ' de Kernighan e Pike, especialmente o Capítulo 8 (veja também Wikipedia no TPOP ).

Esta é a experiência 'been there, done that' - usei essencialmente a técnica descrita em outras respostas onde a compilação não-depuração não vê as instruções do tipo printf por vários anos (mais de uma década). Mas me deparei com o conselho no TPOP (veja meu comentário anterior), e depois habilitei alguns códigos de depuração depois de vários anos, e tive problemas com o contexto alterado, quebrando a depuração. Várias vezes, ter a impressão sempre validada me salvou de problemas posteriores.

Eu uso o NDEBUG para controlar somente asserções e uma macro separada (geralmente DEBUG) para controlar se o rastreamento de depuração está embutido no programa. Mesmo quando o rastreamento de depuração está embutido, eu freqüentemente não quero que a saída de depuração apareça incondicionalmente, então eu tenho mecanismo para controlar se a saída aparece (níveis de depuração, e ao invés de chamar fprintf() diretamente, chamo uma função de depuração que somente condicionalmente imprime para que a mesma compilação do código possa ser impressa ou não impressa com base nas opções do programa). Eu também tenho uma versão de 'subsistema múltiplo' do código para programas maiores, para que eu possa ter diferentes seções do programa produzindo diferentes quantidades de rastreamento - sob controle de tempo de execução.

Eu estou defendendo que, para todas as compilações, o compilador deve ver as instruções de diagnóstico; no entanto, o compilador não gerará nenhum código para as instruções de rastreamento de depuração, a menos que a depuração esteja habilitada. Basicamente, isso significa que todo o seu código é verificado pelo compilador toda vez que você compila - seja para liberação ou depuração. Isto é uma coisa boa!

debug.h - versão 1.2 (1990-05-01)

/*
@(#)File:            $RCSfile: debug.h,v $
@(#)Version:         $Revision: 1.2 $
@(#)Last changed:    $Date: 1990/05/01 12:55:39 $
@(#)Purpose:         Definitions for the debugging system
@(#)Author:          J Leffler
*/

#ifndef DEBUG_H
#define DEBUG_H

/* -- Macro Definitions */

#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)
#endif /* DEBUG */

/* -- Declarations */

#ifdef DEBUG
extern  int     debug;
#endif

#endif  /* DEBUG_H */

debug.h - versão 3.6 (2008-02-11)

/*
@(#)File:           $RCSfile: debug.h,v $
@(#)Version:        $Revision: 3.6 $
@(#)Last changed:   $Date: 2008/02/11 06:46:37 $
@(#)Purpose:        Definitions for the debugging system
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product:        :PRODUCT:
*/

#ifndef DEBUG_H
#define DEBUG_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);

#endif /* DEBUG_H */

Variante de argumento único para C99 ou posterior

Kyle Brandt perguntou:

De qualquer forma, para fazer isso, o debug_print ainda funciona mesmo se não houver argumentos? Por exemplo:

    debug_print("Foo");

Existe um hack simples e antiquado:

debug_print("%s\n", "Foo");

A solução somente do GCC mostrada abaixo também fornece suporte para isso.

No entanto, você pode fazer isso com o sistema C99 reto usando:

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

Comparado com a primeira versão, você perde a verificação limitada que requer o argumento 'fmt', o que significa que alguém poderia tentar chamar 'debug_print ()' sem argumentos (mas a vírgula final na lista de argumentos para fprintf() falharia compilar). Se a perda de verificação é um problema é discutível.

Técnica específica do GCC para um único argumento

Alguns compiladores podem oferecer extensões para outras formas de lidar com listas de argumentos de tamanho variável em macros. Especificamente, como observado pela primeira vez nos comentários de Hugo Ideler , o GCC permite omitir a vírgula que normalmente apareceria após o último argumento 'fixo' para a macro. Ele também permite que você use ##__VA_ARGS__ no texto de substituição de macro, que exclui a vírgula que precede a notação se, mas somente se, o token anterior for uma vírgula:

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)

Essa solução mantém o benefício de exigir o argumento de formato enquanto aceita argumentos opcionais após o formato.

Essa técnica também é suportada pelo Clang para compatibilidade com o GCC.

Por que o loop do-while?

Qual é o propósito do do while aqui?

Você deseja poder usar a macro para que ela pareça uma chamada de função, o que significa que ela será seguida por um ponto e vírgula. Portanto, você precisa empacotar o corpo da macro para se adequar. Se você usar uma declaração if sem o rodeio do { ... } while (0) , você terá:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) fprintf(stderr, __VA_ARGS__)

Agora, suponha que você escreva:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

Infelizmente, esse recuo não reflete o controle real do fluxo, porque o pré-processador produz um código equivalente a esse (recuo e contraventamentos adicionados para enfatizar o significado real):

if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

A próxima tentativa na macro pode ser:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

E o mesmo fragmento de código agora produz:

if (x > y)
    if (DEBUG)
    {
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    }
; // Null statement from semi-colon after macro
else
    do_something_useful(x, y);

E o else é agora um erro de sintaxe. O loop do { ... } while(0) evita esses dois problemas.

Existe uma outra maneira de escrever a macro que pode funcionar:

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

Isso deixa o fragmento do programa mostrado como válido. O elenco (void) impede que ele seja usado em contextos onde um valor é necessário - mas pode ser usado como o operando à esquerda de um operador de vírgula onde a versão do { ... } while (0) não pode. Se você acha que deve incorporar código de depuração a essas expressões, talvez prefira isso. Se você preferir exigir que a impressão de depuração funcione como uma instrução completa, a versão do { ... } while (0) é melhor. Observe que, se o corpo da macro envolver qualquer ponto e vírgula (grosso modo), você só poderá usar a notação do { ... } while(0) . Isso sempre funciona; o mecanismo de declaração de expressão pode ser mais difícil de aplicar. Você também pode receber avisos do compilador com o formulário de expressão que prefere evitar; isso dependerá do compilador e dos flags que você usa.

A TPOP estava anteriormente em http://plan9.bell-labs.com/cm/cs/tpop e http://cm.bell-labs.com/cm/cs/tpop mas ambas estão agora (2015-08-10) quebrado.

Código no GitHub

Se você está curioso, você pode olhar para este código no GitHub no meu SOQ ( Questions) como arquivos debug.c , debug.h e mddebug.c no mddebug.c src/libsoq .


Aqui está a versão que eu uso:

#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif

Então, quando uso o gcc, eu gosto:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

Porque pode ser inserido no código.

Suponha que você esteja tentando depurar

printf("%i\n", (1*2*3*4*5*6));

720

Então você pode alterá-lo para:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

E você pode obter uma análise de qual expressão foi avaliada para o quê.

Está protegido contra o problema da avaliação dupla, mas a ausência de gens-clubes deixa-o aberto a colisões de nomes.

No entanto, aninha-se:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

Então, acho que, desde que você evite usar o g2rE3 como um nome de variável, você ficará bem.

Certamente eu encontrei (e versões aliadas para seqüências de caracteres e versões para níveis de depuração etc) inestimável.


Eu estive pensando em como fazer isso por anos e, finalmente, chegar a uma solução. No entanto, eu não sabia que havia outras soluções aqui já. Primeiro, na diferença com a resposta de Leffler , não vejo seu argumento de que as impressões de depuração devam sempre ser compiladas. Eu prefiro não ter toneladas de código desnecessário em execução no meu projeto, quando não for necessário, nos casos em que eu preciso testar e eles podem não estar sendo otimizados.

Não compilar toda vez pode parecer pior do que na prática atual. Você acaba com impressões de depuração que não são compiladas às vezes, mas não é tão difícil compilá-las e testá-las antes de finalizar um projeto. Com este sistema, se você estiver usando três níveis de depuração, apenas coloque-o no nível 3 da mensagem de depuração, corrija seus erros de compilação e verifique se há algum outro antes de finalizar seu código. (Como é claro, a compilação de instruções de depuração não garante que eles ainda estejam funcionando conforme o esperado.)

Minha solução também fornece níveis de detalhes de depuração; e se você configurá-lo para o nível mais alto, todos eles compilarão. Se você usou um nível de detalhes de depuração alto recentemente, todos eles puderam compilar naquele momento. Atualizações finais devem ser bem fáceis. Eu nunca precisei de mais de três níveis, mas Jonathan diz que ele usou nove. Esse método (como o de Leffler) pode ser estendido para qualquer número de níveis. O uso do meu método pode ser mais simples; requerendo apenas duas instruções quando usado em seu código. Eu estou, no entanto, codificando a macro CLOSE também - embora não faça nada. Poderia se eu estivesse enviando para um arquivo.

Contra o custo, o passo extra de testá-los para ver que eles vão compilar antes da entrega, é que

  1. Você deve confiar neles para ficar otimizado, o que deve acontecer se você tiver um nível de otimização suficiente.
  2. Além disso, eles provavelmente não o farão se você fizer uma compilação de release com otimização desativada para fins de teste (o que é reconhecidamente raro); e eles quase certamente não o farão durante a depuração - executando dezenas ou centenas de instruções "if (DEBUG)" em tempo de execução; retardando assim a execução (que é minha principal objeção) e, menos importante, aumentando o tamanho do seu executável ou dll; e, portanto, os tempos de execução e compilação. Jonathan, no entanto, me informa que seu método pode ser feito para não compilar declarações.

Filiais são realmente relativamente caras nos modernos processadores de pré-busca. Talvez não seja um grande problema se o seu aplicativo não for de tempo crítico; mas se o desempenho é um problema, então, sim, um negócio grande o bastante para eu preferir optar por um código de depuração um tanto mais rápido (e possivelmente um lançamento mais rápido, em casos raros, como observado).

Então, o que eu queria é uma macro de impressão de depuração que não seja compilada se não for impressa, mas sim se for. Eu também queria níveis de depuração, de modo que, por exemplo, se eu quisesse partes essenciais do código para não imprimir em alguns momentos, mas para imprimir em outros, eu poderia definir um nível de depuração, e ter impressões extras de depuração. veio através de uma maneira de implementar os níveis de depuração que determinaram se a impressão foi mesmo compilada ou não. Eu consegui assim:

DebugLog.h:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

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

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

Usando as macros

Para usá-lo, basta fazer:

DEBUGLOG_INIT("afile.log");

Para gravar no arquivo de log, basta fazer:

DEBUGLOG_LOG(1, "the value is: %d", anint);

Para fechá-lo, você faz:

DEBUGLOG_CLOSE();

embora atualmente isso não seja necessário, tecnicamente falando, já que não faz nada. Eu ainda estou usando o CLOSE agora, no entanto, caso eu mude de idéia sobre como funciona, e quero deixar o arquivo aberto entre as instruções de log.

Então, quando você quiser ativar a impressão de depuração, basta editar o primeiro #define no arquivo de cabeçalho, por exemplo,

#define DEBUG 1

Para ter instruções de log compiladas para nada, faça

#define DEBUG 0

Se você precisar de informações de um trecho de código executado com frequência (ou seja, um alto nível de detalhes), talvez queira escrever:

 DEBUGLOG_LOG(3, "the value is: %d", anint);

Se você definir o DEBUG como 3, os níveis de registro 1, 2 e 3 serão compilados. Se você configurá-lo como 2, obterá os níveis de registro 1 e 2. Se você defini-lo como 1, receberá apenas instruções de nível 1 de registro.

Quanto ao loop do-while, já que isso é avaliado como uma única função ou nada, em vez de uma instrução if, o loop não é necessário. Ok, castigue-me por usar C em vez de C ++ IO (e QStr's :: arg () do Qt é uma forma mais segura de formatar variáveis ​​quando em Qt, também - é bem legal, mas pega mais código e a documentação de formatação não é organizada como poderia ser - mas ainda assim eu encontrei casos em que é preferível), mas você pode colocar qualquer código no arquivo .cpp que você deseja. Também pode ser uma classe, mas você precisa instanciá-la e mantê-la, ou fazer uma nova () e armazená-la. Dessa forma, você simplesmente solta as instruções #include, init e opcionalmente close em sua fonte, e você está pronto para começar a usá-la. Seria uma boa aula, no entanto, se você é tão inclinado.

Eu já tinha visto muitas soluções, mas nenhuma se adequava tanto aos meus critérios quanto a esta.

  1. Pode ser estendido para fazer quantos níveis quiser.
  2. Compila a nada se não imprimir.
  3. Ele centraliza o IO em um local fácil de editar.
  4. É flexível, usando formatação printf.
  5. Novamente, isso não diminui a velocidade das execuções de depuração, enquanto as impressões de depuração sempre compiladas são sempre executadas no modo de depuração. Se você estiver fazendo ciência da computação e não for mais fácil escrever o processamento de informações, poderá se deparar com um simulador que consome CPU, para ver, por exemplo, onde o depurador o interrompe com um índice fora do intervalo para um vetor. Estes correm extra-lentamente no modo de depuração já. A execução obrigatória de centenas de impressões de depuração irá necessariamente atrasar ainda mais essas execuções. Para mim, essas corridas não são incomuns.

Não é muito significativo, mas além disso:

  1. Não requer nenhum truque para imprimir sem argumentos (por exemplo, DEBUGLOG_LOG(3, "got here!"); ); permitindo assim que você use, por exemplo, a formatação .arg () mais segura do Qt. Ele funciona no MSVC e, portanto, provavelmente no gcc. Ele usa ## em #define s, o que não é padrão, como Leffler aponta, mas é amplamente suportado. (Você pode recodificar para não usar ## se necessário, mas você terá que usar um hack como ele fornece.)

Aviso: Se você esquecer de fornecer o argumento de nível de log, o MSVC afirma que o identificador não está definido.

Você pode querer usar um nome de símbolo de pré-processador diferente de DEBUG, já que alguma fonte também define esse símbolo (por exemplo, progs usando os comandos ./configure para se preparar para a construção). Pareceu-me natural quando a desenvolvi. Eu o desenvolvi em um aplicativo onde a DLL está sendo usada por outra coisa, e é mais comum enviar impressões de log para um arquivo; mas alterá-lo para vprintf () também funcionaria bem.

Espero que isso evite que muitos de vocês pensem em descobrir a melhor maneira de fazer o log de depuração; ou mostra um que você pode preferir. Eu, sem entusiasmo, tenho tentado descobrir isso por décadas. Funciona no MSVC 2012 e 2015 e, portanto, provavelmente no gcc; bem como, provavelmente, trabalhando em muitos outros, mas eu não testei isso neles.

Eu quero fazer uma versão streaming deste dia, também.

Nota: Obrigado a Leffler, que cordialmente me ajudou a formatar melhor minha mensagem para o .


Eu uso algo assim:

#ifdef DEBUG
 #define D if(1) 
#else
 #define D if(0) 
#endif

Do que eu uso apenas D como prefixo:

D printf("x=%0.3f\n",x);

O compilador vê o código de depuração, não há problema de vírgula e funciona em todos os lugares. Também funciona quando o printf não é suficiente, digamos quando você deve descarregar um array ou calcular algum valor de diagnóstico que é redundante para o próprio programa.

EDIT: Ok, pode gerar um problema quando há else algum lugar perto que pode ser interceptado por este injetado if . Esta é uma versão que diz sobre isso:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif

Isso é o que eu uso:

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

Ele tem o bom benefício de manipular o printf corretamente, mesmo sem argumentos adicionais. No caso de DBG == 0, até mesmo o compilador mais idiota não recebe nada para mastigar, então nenhum código é gerado.


Para uma implementação portátil (ISO C90), você poderia usar parênteses duplos, como este;

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

#ifndef NDEBUG
#  define debug_print(msg) stderr_printf msg
#else
#  define debug_print(msg) (void)0
#endif

void
stderr_printf(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
}

int
main(int argc, char *argv[])
{
  debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
  return 0;
}

ou (hackish, não recomendaria)

#include <stdio.h>

#define _ ,
#ifndef NDEBUG
#  define debug_print(msg) fprintf(stderr, msg)
#else
#  define debug_print(msg) (void)0
#endif

int
main(int argc, char *argv[])
{
  debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
  return 0;
}

Acredito que essa variação do tema forneça categorias de depuração sem a necessidade de ter um nome de macro separado por categoria.

Eu usei essa variação em um projeto do Arduino, onde o espaço do programa é limitado a 32K e a memória dinâmica é limitada a 2K. A adição de instruções de depuração e de cadeias de depuração de rastreio usa rapidamente espaço. Portanto, é essencial poder limitar o rastreio de depuração incluído no tempo de compilação ao mínimo necessário sempre que o código for criado.

debug.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

chamando o arquivo .cpp

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...




c-preprocessor