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?
c
c-preprocessor
jfarrell
fonte
fonte
Respostas:
Se você usar um compilador C99 ou posterior
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:
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: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:
E então, no código, escreva:
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:
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)
debug.h - versão 3.6 (11-02-2008)
Variante de argumento único para C99 ou posterior
Kyle Brandt perguntou:
Há um truque simples e antiquado:
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:
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: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?
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 envolventedo { ... } while (0)
, terá:Agora, suponha que você escreva:
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):
A próxima tentativa na macro pode ser:
E o mesmo fragmento de código agora produz:
E
else
agora é um erro de sintaxe. Odo { ... } while(0)
loop evita esses dois problemas.Há uma outra maneira de escrever a macro que pode funcionar:
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 ado { ... } 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, ado { ... } 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 ado { ... } 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
emddebug.c
no src / libsoq sub-diretório.fonte
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__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 usandofprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
The ## __ VA_ARGS__ permite passar nenhum parâmetro para a função.#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ê usardebug_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'.-D input=4,macros=9,rules=2
para 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.Eu uso algo como isto:
Do que eu apenas uso D como prefixo:
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
printf
nã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
else
algum lugar próximo que possa ser interceptado por este injetadoif
. Esta é uma versão que aborda isso:fonte
for(;0;)
isso, pode gerar um problema quando você escreve algo comoD continue;
ouD break;
.Para uma implementação portátil (ISO C90), você pode usar parênteses duplos, como este;
ou (hackish, não recomendaria)
fonte
Aqui está a versão que eu uso:
fonte
Eu faria algo como
Eu acho que isso é mais limpo.
fonte
assert()
do stdlib funciona da mesma maneira e eu normalmente apenas re-utilizar aNDEBUG
macro para o meu próprio código de depuração ...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");
.fonte
fonte
Isto é o que eu uso:
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.
fonte
O meu favorito abaixo é o
var_dump
que, 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
fonte
Então, ao usar o gcc, eu gosto de:
Porque pode ser inserido no código.
Suponha que você esteja tentando depurar
Então você pode alterá-lo para:
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:
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.
fonte
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
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:
DebugLog.cpp:
Usando as macros
Para usá-lo, basta fazer:
Para escrever no arquivo de log, basta:
Para fechá-lo, você faz:
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,
Para que as instruções de log sejam compiladas para nada, faça
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:
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.
Não é muito significativo, mas além disso:
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#define
s, 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
./configure
comandos 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.
fonte
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).((void)0)
- é fácil.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
chamando o arquivo .cpp
fonte