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_print
ainda 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 while
aqui?
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 if
declaraçã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 else
agora é 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.h
e mddebug.c
no
src / libsoq
sub-diretório.