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


209

Tentando criar uma macro que pode ser usada para imprimir mensagens de depuração quando 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?


O compilador (gcc) otimiza instruções como if (DEBUG) {...} fora, se no código de produção a macro DEBUG estiver definida como 0? Entendo que existem boas razões para deixar as instruções de depuração visíveis para o compilador, mas permanece um mau pressentimento. #Pat
Pat

Respostas:


410

Se você usar um compilador C99 ou posterior

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

Ele pressupõe que você esteja usando C99 (a notação de lista de argumentos variáveis ​​não é suportada nas versões anteriores). O do { ... } while (0)idioma garante que o código atue 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 DEBUG for 0.

Se você deseja 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 DEBUG.

Se você insistir em um literal cadeia de caracteres para a cadeia de formato (provavelmente uma boa idéia 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 um string de formato maior do que o programador escreve.

Se você usar um compilador C89

Se você está preso ao C89 e não há uma extensão útil do compilador, 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 uma 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ó chama a função de impressão se a macro DEBUG avaliar como diferente de zero.

Isso requer uma função de suporte - dbg_printf () no exemplo - para lidar com 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 __VA_ARGS__técnica é mais organizada porque usa notação de função regular, não o hack de parênteses duplos.

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

[ Repensando os comentários feitos para outra resposta. ]

Uma idéia 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ódigos de longo prazo - códigos que durarão uma década ou duas.

Suponha que um pedaço de código esteja praticamente inativo (estável) por vários anos, mas agora precise ser alterado. Você reativa o rastreamento de depuração - mas é frustrante precisar depurar o código de depuração (rastreamento) porque se refere a variáveis ​​que foram renomeadas ou redigitadas durante os anos de manutenção estável. Se o compilador (pós-processador anterior) sempre vê a declaração de impressão, isso garante que quaisquer alterações ao redor não invalidem o diagnóstico. Se o compilador não vir a declaração impressa, não poderá protegê-lo contra o seu próprio descuido (ou o descuido de seus colegas ou colaboradores). Veja ' The Practice of Programming ' de Kernighan e Pike, especialmente o Capítulo 8 (veja também a Wikipedia sobre TPOP ).

Essa é a experiência 'estive lá, fiz essa' - usei essencialmente a técnica descrita em outras respostas, nas quais a compilação sem depuração não vê as declarações do tipo printf por vários anos (mais de uma década). Mas me deparei com o conselho do TPOP (veja meu comentário anterior) e, em seguida, habilitei algum código de depuração após vários anos e me deparei com problemas de contexto alterado que quebravam a depuração. Várias vezes, ter a impressão sempre validada me salvou de problemas posteriores.

Eu uso o NDEBUG para controlar apenas asserções e uma macro separada (geralmente DEBUG) para controlar se o rastreamento de depuração está incorporado no programa. Mesmo quando o rastreamento de depuração é incorporado, geralmente não quero que a saída de depuração apareça incondicionalmente, por isso tenho um mecanismo para controlar se a saída aparece (níveis de depuração e, em vez de chamar fprintf()diretamente, chamo uma função de impressão de depuração que imprime condicionalmente para que a mesma compilação do código possa ser impressa ou não com base nas opções do programa). Também tenho uma versão do código de 'subsistema múltiplo' 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.

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 ativada. 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 (11-02-2008)

/*
@(#)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:

Enfim, para fazer isso, debug_printainda funciona mesmo se não houver argumentos? Por exemplo:

    debug_print("Foo");

Há um truque simples e antiquado:

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

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

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

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

Comparado à 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 à direita na lista de argumentos fprintf()falharia ao 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 maneiras de lidar com listas de argumentos de comprimento 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' da macro. Também permite usar ##__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)

Esta solução mantém o benefício de exigir o argumento de formato e aceitar 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 objetivo do do whileaqui?

Você deseja usar a macro para que ela se pareça com uma chamada de função, o que significa que será seguida por ponto e vírgula. Portanto, você precisa empacotar o corpo da macro para se adequar. Se você usar uma ifdeclaração sem a envolvente do { ... } while (0), 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 código equivalente a este (recuo e chaves 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 elseagora é um erro de sintaxe. O do { ... } while(0)loop evita esses dois problemas.

Há 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. A (void)conversão impede que seja usada em contextos em que um valor é necessário - mas pode ser usado como o operando esquerdo de um operador de vírgula, onde a do { ... } while (0)versão não pode. Se você acha que deve poder incorporar o código de depuração em tais expressões, talvez prefira isso. Se você preferir exigir que a impressão de depuração atue como uma declaração completa, a do { ... } while (0)versão será melhor. Observe que, se o corpo da macro envolver algum ponto e vírgula (grosso modo), você poderá usar apenas a do { ... } while(0)notação. 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 sinalizadores que você usa.


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


Código no GitHub

Se você estiver curioso, você pode olhar para este código no GitHub em meus SOQ (Perguntas Stack Overflow) repositório como arquivos debug.c, debug.he mddebug.cno src / libsoq sub-diretório.


1
Acho que vale a pena mencionar a abordagem GCC ## - gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html sob o título "Variante C99 de argumento único".
Hugo Ideler 16/03/2012

2
Anos mais tarde, e essa resposta ainda é a mais útil de todas as internets, sobre como usar o apelido printk! O vfprintf não funciona no espaço do kernel, pois o stdio não está disponível. Obrigado! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
Kevinf

6
No seu exemplo com as palavras __FILE__, __LINE__, __func__, __VA_ARGS__- chave , ele não será compilado se você não tiver parâmetros printf, ou seja, se você apenas chamar. debug_print("Some msg\n"); Você pode corrigir isso usando fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); The ## __ VA_ARGS__ permite passar nenhum parâmetro para a função.
Mc_electron

1
@ LogicTom: a diferença é entre #define debug_print(fmt, ...)e #define debug_print(...). O primeiro deles requer pelo menos um argumento, o formato string ( fmt) e zero ou mais outros argumentos; o segundo requer zero ou mais argumentos no total. Se você usar debug_print()o primeiro, receberá um erro do pré-processador sobre o uso indevido da macro, enquanto o segundo não. No entanto, você ainda recebe erros de compilação porque o texto de substituição não é válido C. Portanto, realmente não faz muita diferença - daí o uso do termo 'verificação limitada'.
Jonathan Leffler

1
A variante mostrada acima, @ St.Antario, usa um único nível de depuração ativo em todo o aplicativo, e geralmente uso opções de linha de comando para permitir que o nível de depuração seja definido quando o programa for executado. Eu também tenho uma variante que reconhece vários subsistemas diferentes, cada um dos quais recebe um nome e seu próprio nível de depuração, para que eu possa usar -D input=4,macros=9,rules=2para definir o nível de depuração do sistema de entrada como 4, o sistema de macros para 9 (passando por intenso escrutínio) ) e o sistema de regras para 2. Existem inúmeras variações sobre o tema; use o que mais lhe convier.
Jonathan Leffler

28

Eu uso algo como isto:

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

Do que eu apenas uso 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 qualquer lugar. Também funciona quando printfnão é suficiente, digamos quando você deve despejar uma matriz ou calcular algum valor de diagnóstico redundante para o próprio programa.

EDIT: Ok, pode gerar um problema quando houver elsealgum lugar próximo que possa ser interceptado por este injetado if. Esta é uma versão que aborda isso:

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

3
Quanto a for(;0;)isso, pode gerar um problema quando você escreve algo como D continue;ou D break;.
ACcreator

1
Me pegou; parece muito improvável que possa ocorrer em acidente, no entanto.
MBq

11

Para uma implementação portátil (ISO C90), você pode 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;
}

3
@LB: para fazer o pré-processador "pensar", há apenas um argumento, enquanto deixa _ ser expandido posteriormente.
Marcin Koziuk 29/10/09

10

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

9

Eu faria algo como

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

Eu acho que isso é mais limpo.


Eu realmente não gosto da idéia de usar uma macro dentro de um teste como uma bandeira. Você poderia explicar por que a impressão de depuração deve ser sempre verificada?
LB40 29/10/09

1
@ Jonathan: Se o código só é executado no modo de depuração, por que você deveria se importar se ele compilar no modo não de depuração? assert()do stdlib funciona da mesma maneira e eu normalmente apenas re-utilizar a NDEBUGmacro para o meu próprio código de depuração ...
Christoph

usando DEBUG no teste, se alguém fizer um undef DEBUG não controlado, seu código não será mais compilado. certo ?
LB40 29/10/09

4
É frustrante habilitar a depuração e, em seguida, é necessário depurar o código de depuração porque se refere a variáveis ​​que foram renomeadas ou redigitadas, etc. Se o compilador (pós-processador) sempre vê a instrução print, garante que todas as alterações ao redor tenham ocorrido. não invalidou o diagnóstico. Se o compilador não vir a declaração impressa, não poderá protegê-lo contra o seu próprio descuido (ou o descuido de seus colegas ou colaboradores). Veja 'The Practice of Programming', de Kernighan e Pike - plan9.bell-labs.com/cm/cs/tpop .
31411 Jonathan Leffler

1
@Christoph: bem, mais ou menos ... Eu uso o NDEBUG para controlar apenas as asserções e uma macro separada (geralmente DEBUG) para controlar o rastreamento de depuração. Frequentemente, não quero que a saída de depuração apareça incondicionalmente; portanto, tenho um mecanismo para controlar se a saída aparece (níveis de depuração e, em vez de chamar diretamente fprintf (), chamo uma função de impressão de depuração que imprime condicionalmente apenas a mesma compilação do código pode imprimir ou não imprimir com base nas opções do programa). Estou defendendo que, para todas as compilações, o compilador deve ver as instruções de diagnóstico; no entanto, ele não gerará código, a menos que a depuração esteja ativada.
31411 Jonathan Leffler

8

De acordo com http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , deve haver um ##antes __VA_ARGS__.

Caso contrário, uma macro #define dbg_print(format, ...) printf(format, __VA_ARGS__)não irá compilar o exemplo a seguir: dbg_print("hello world");.


1
Bem-vindo ao Stack Overflow. Você está certo de que o GCC tem a extensão não padrão que você faz referência. A resposta atualmente aceita de fato menciona isso, incluindo exatamente o URL de referência que você fornece.
Jonathan Leffler

7
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)

Qual versão do C suporta essa notação? E, se funcionou, o token colando todos os argumentos como esse significa que você tem apenas um conjunto muito limitado de opções para a string de formato, não é?
31411 Jonathan Leffler

@Jonathan: gcc (Debian 4.3.3-13) 4.3.3
eyalm

1
OK - concordado: está documentado como uma extensão antiga do GNU (seção 5.17 do manual do GCC 4.4.1). Mas você provavelmente deve documentar que funcionará apenas com o GCC - ou talvez tenhamos feito isso entre nós nesses comentários.
31411 Jonathan Leffler

1
Minha intenção era mostrar um outro estilo de usar argumentos e, principalmente, para demonstrar o uso de FUNÇÃO e LINHA
eyalm

2

Isto é o que eu uso:

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

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


É melhor que o compilador sempre verifique o código de depuração.
Jonathan Leffler

1

O meu favorito abaixo é o var_dumpque, quando chamado como:

var_dump("%d", count);

produz resultados como:

patch.c:150:main(): count = 0

Crédito para @ Jonathan Leffler. Todos são felizes em C89:

Código

#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf x; }} while (0)

/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)

#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
    __FILE__,  __LINE__, __func__); }} while (0)

1

Então, ao usar o gcc, eu gosto de:

#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 quê.

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

No entanto, aninha:

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 g2rE3 como um nome de variável, você ficará bem.

Certamente eu achei isso (e versões aliadas para strings, e versões para níveis de depuração etc) inestimáveis.


1

Estive estudando sobre como fazer isso há anos e finalmente consegui uma solução. No entanto, eu não sabia que já havia outras soluções aqui. Primeiro, ao contrário da resposta de Leffler , não vejo seu argumento de que as impressões de depuração sempre devem ser compiladas. Prefiro não ter toneladas de código desnecessário em execução no meu projeto, quando não necessário, nos casos em que preciso testar e eles podem não estar sendo otimizados.

Não compilar todas as vezes pode parecer pior do que na prática real. Você acaba com impressões de depuração que às vezes não são compiladas, 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, basta colocá-lo no nível três da mensagem de depuração, corrija seus erros de compilação e verifique se há outros antes de finalizar seu código. (Como é claro, a compilação de instruções de depuração não garante que elas ainda estejam funcionando conforme o esperado.)

Minha solução também fornece níveis de detalhes de depuração; e se você definir o nível mais alto, todos serão compilados. Se você está usando um alto nível de detalhes de depuração recentemente, todos eles foram capazes de 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 já usou nove. Este 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; exigindo apenas duas instruções quando usadas no seu código. No entanto, também estou codificando a macro CLOSE - embora ela não faça nada. Talvez se eu estivesse enviando para um arquivo.

Contra o custo, a etapa extra de testá-los para verificar se eles serão compilados antes da entrega é que

  1. Você deve confiar neles para obter otimização, o que deve acontecer se você tiver um nível de otimização suficiente.
  2. Além disso, provavelmente não o farão se você compilar uma versão com a 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 assim dezenas ou centenas de instruções "if (DEBUG)" em tempo de execução; diminuindo a execução (que é a minha principal objeção) e, menos importante, aumentando o tamanho do executável ou da dll; e, portanto, tempos de execução e compilação. Jonathan, no entanto, me informa que seu método pode ser adotado para também não compilar declarações.

As filiais são realmente relativamente caras nos modernos processadores de pré-busca. Talvez não seja grande coisa se seu aplicativo não for crítico em termos de tempo; mas se o desempenho for um problema, sim, é um negócio grande o suficiente para que eu prefira optar por um código de depuração de execução um pouco mais rápida (e possivelmente uma liberação mais rápida, 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 para ser impressa, mas sim se for. Eu também queria níveis de depuração, de modo que, por exemplo, se eu quisesse que partes cruciais do código do desempenho não fossem impressas em alguns momentos, mas em outras, eu poderia definir um nível de depuração e ter impressões extras de depuração. deparei com uma maneira de implementar níveis de depuração que determinavam se a impressão era compilada ou não. 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 escrever no arquivo de log, basta:

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, pois não faz nada. No entanto, ainda estou usando o CLOSE agora, caso mude de idéia sobre como ele funciona e que eu queira deixar o arquivo aberto entre as instruções de log.

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

#define DEBUG 1

Para que as instruções de log sejam compiladas para nada, faça

#define DEBUG 0

Se você precisar de informações de um trecho de código executado com freqüência (ou seja, um alto nível de detalhe), escreva:

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

Se você definir DEBUG como 3, os níveis de log 1, 2 e 3 serão compilados. Se você defini-lo como 2, obtém os níveis de registro 1 e 2. Se você defini-lo como 1, obtém apenas instruções de nível 1.

Quanto ao loop do-while, como esse valor é avaliado como uma única função ou para 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 QString :: arg () do Qt é uma maneira mais segura de formatar variáveis ​​quando no Qt também - é bem liso, mas exige mais código e a documentação de formatação não é tão organizada como pode ser - mas ainda assim encontrei casos em que é preferível), mas você pode colocar qualquer código no arquivo .cpp que desejar. Também pode ser uma classe, mas você precisará instanciar e acompanhar, ou fazer um novo () e armazená-lo. Dessa forma, você apenas solta as instruções #include, init e close opcionalmente em sua fonte e está pronto para começar a usá-las. Seria uma boa aula, no entanto, se você é tão inclinado.

Eu já tinha visto muitas soluções, mas nenhuma se encaixava nos meus critérios e também nesta.

  1. Pode ser estendido para fazer quantos níveis você desejar.
  2. Ele não compila se não estiver imprimindo.
  3. Ele centraliza o IO em um local fácil de editar.
  4. É flexível, usando a formatação printf.
  5. Novamente, ele 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ê está fazendo ciência da computação e não é mais fácil escrever o processamento de informações, pode se encontrar executando um simulador que consome CPU, para ver, por exemplo, onde o depurador o interrompe com um índice fora do intervalo de um vetor. Eles são executados muito lentamente no modo de depuração. A execução obrigatória de centenas de impressões de depuração necessariamente diminuirá ainda mais essas execuções. Para mim, essas execuções não são incomuns.

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

  1. Não requer hack 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. Funciona no MSVC e, portanto, provavelmente no gcc. Ele usa ##no #defines, que não é padrão, como Leffler aponta, mas é amplamente suportado. (Você pode recodificá-lo para não usá-lo, ##se necessário, mas precisará usar um hack, como ele fornece.)

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

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

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

Também pretendo fazer uma versão em streaming deste dia.

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


2
Você diz "executando dezenas ou centenas de if (DEBUG)instruções em tempo de execução, que não são otimizadas" - que é inclinado nos moinhos de vento . O ponto principal do sistema que descrevi é que o código é verificado pelo compilador (importante e automático - nenhuma compilação especial é necessária), mas o código de depuração não é gerado porque é otimizado (portanto, o impacto no tempo de execução é zero). tamanho ou desempenho do código, porque o código não está presente no tempo de execução).
22816 Jonathan

Jonathan Leffler: Agradecido por apontar minhas palavras erradas. Eu deixei meus pensamentos correrem mais rápido que meus dedos, sendo tão feliz por ter terminado. Revisei minhas objeções com "... 1) você deve confiar nelas para obter otimização, o que deve acontecer se você tiver um nível de otimização suficiente. 2) Além disso, elas não acontecerão se você fizer uma compilação com otimização desativado para fins de teste; e provavelmente não funcionará durante a depuração - executando dezenas ou centenas de instruções 'if (DEBUG)' em tempo de execução - aumentando assim o tamanho do executável ou da dll e o tempo de execução ".
CodeLurker 8/16

Para você fazer a outra coisa importante que a minha está fazendo, você teria que ter níveis de depuração. Embora muitas vezes eu não precise de muitos deles ativados, alguns aplicativos realmente se beneficiam ao obter um ótimo nível de detalhes sobre um loop de tempo crítico com um simples "#define DEBUG 3" e depois voltar para muito menos informações detalhadas com "#define DEBUG 1". Eu nunca precisei de mais de três níveis e, portanto, pelo menos aproximadamente 1/3 dos meus erros já são compilados no lançamento. Se eu usei o nível 3 recentemente, provavelmente todos o fazem.
CodeLurker 8/16

YMMV. O sistema moderno que mostrei suporta a configuração dinâmica (tempo de execução) dos níveis de depuração, para que você possa decidir programaticamente quanto da depuração é produzida no tempo de execução. Normalmente, usei os níveis 1 a 9, embora não haja limite superior (ou limite inferior; o nível padrão é 0, que geralmente está desativado, mas pode ser solicitado explicitamente durante o desenvolvimento ativo, se apropriado - não é apropriado para trabalhos de longo prazo). Eu escolhi um nível padrão de 3; as coisas podem ser ajustadas. Isso me dá muito controle. Se você realmente não deseja testar o código de depuração quando inativo, altere a alternativa para ((void)0)- é fácil.
Jonathan Leffler

1
Ahh Teria ajudado a ler a coisa toda. É um post bastante longo. Eu acho que isso tem os pontos essenciais até agora. Acontece que o seu, como o meu, pode ser usado para compilar ou não todas as impressões de depuração e pode suportar níveis; embora seja certo que o seu pode compilar níveis que você não está usando - a um custo durante a depuração.
CodeLurker 8/11/16

0

Eu acredito que essa variação do tema fornece 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, em que 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 seqüências de caracteres de depuração de rastreamento rapidamente consome espaço. Portanto, é essencial poder limitar o rastreamento 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");
...
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.