Si usa un compilador C99 o posterior
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Se supone que está utilizando C99 (la notación de la lista de argumentos variables no es compatible con versiones anteriores). El do { ... } while (0)
idioma asegura que el código actúa como una declaración (llamada a función). El uso incondicional del código asegura que el compilador siempre verifique que su código de depuración sea válido, pero el optimizador eliminará el código cuando DEBUG sea 0.
Si desea trabajar con #ifdef DEBUG, cambie la condición de prueba:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
Y luego use DEBUG_TEST donde usé DEBUG.
Si insiste en un literal de cadena para la cadena de formato (probablemente una buena idea de todos modos), también puede introducir cosas como __FILE__
, __LINE__
y __func__
en la salida, que pueden mejorar los diagnósticos:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Esto se basa en la concatenación de cadenas para crear una cadena de formato más grande que la que escribe el programador.
Si usa un compilador C89
Si está atascado con C89 y no tiene una extensión útil del compilador, entonces no hay una forma particularmente limpia de manejarlo. La técnica que solía usar era:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
Y luego, en el código, escriba:
TRACE(("message %d\n", var));
Los paréntesis dobles son cruciales, y es por eso que tiene la notación divertida en la expansión de macro. Como antes, el compilador siempre verifica la validez sintáctica del código (lo cual es bueno) pero el optimizador solo invoca la función de impresión si la macro DEBUG evalúa a un valor distinto de cero.
Esto requiere una función de soporte, dbg_printf () en el ejemplo, para manejar cosas como 'stderr'. Requiere que sepas cómo escribir funciones varargs, pero eso no es 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);
}
También puede usar esta técnica en C99, por supuesto, pero la __VA_ARGS__
técnica es más ordenada porque utiliza la notación de función regular, no el hack de paréntesis dobles.
¿Por qué es crucial que el compilador siempre vea el código de depuración?
[ Repitiendo los comentarios hechos a otra respuesta. ]
Una idea central detrás de las implementaciones C99 y C89 anteriores es que el compilador propiamente dicho siempre ve las declaraciones de depuración tipo printf. Esto es importante para el código a largo plazo, código que durará una o dos décadas.
Supongamos que un fragmento de código ha estado mayormente inactivo (estable) durante varios años, pero ahora necesita ser cambiado. Vuelve a habilitar el seguimiento de depuración, pero es frustrante tener que depurar el código de depuración (seguimiento) porque se refiere a variables que se han renombrado o reescrito, durante los años de mantenimiento estable. Si el compilador (postprocesador posterior) siempre ve la declaración de impresión, se asegura de que los cambios no hayan invalidado los diagnósticos. Si el compilador no ve la declaración impresa, no puede protegerlo contra su propio descuido (o el descuido de sus colegas o colaboradores). Consulte ' La práctica de la programación ' de Kernighan y Pike, especialmente el Capítulo 8 (consulte también Wikipedia en TPOP ).
Esta es la experiencia de 'estado allí, hecho eso': utilicé esencialmente la técnica descrita en otras respuestas donde la compilación sin depuración no ve las declaraciones tipo printf durante varios años (más de una década). Pero me encontré con el consejo en TPOP (vea mi comentario anterior), y luego habilité un código de depuración después de varios años, y me encontré con problemas de cambio de contexto que rompieron la depuración. Varias veces, tener la impresión siempre validada me ha salvado de problemas posteriores.
Uso NDEBUG solo para controlar aserciones, y una macro separada (generalmente DEBUG) para controlar si el seguimiento de depuración está integrado en el programa. Incluso cuando el seguimiento de depuración está integrado, con frecuencia no quiero que la salida de depuración aparezca incondicionalmente, por lo que tengo un mecanismo para controlar si aparece la salida (niveles de depuración y, en lugar de llamar fprintf()
directamente, llamo a una función de impresión de depuración que solo imprime condicionalmente para que la misma compilación del código se pueda imprimir o no según las opciones del programa). También tengo una versión de 'subsistema múltiple' del código para programas más grandes, de modo que puedo tener diferentes secciones del programa que producen diferentes cantidades de rastreo, bajo control de tiempo de ejecución.
Estoy abogando por que para todas las compilaciones, el compilador debería ver las declaraciones de diagnóstico; sin embargo, el compilador no generará ningún código para las declaraciones de rastreo de depuración a menos que la depuración esté habilitada. Básicamente, significa que todo el código es verificado por el compilador cada vez que compila, ya sea para su liberación o depuración. ¡Ésto es una cosa buena!
debug.h - versión 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 - versión 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 o posterior
Kyle Brandt preguntó:
De todos modos, hacer esto debug_print
aún funciona, incluso si no hay argumentos. Por ejemplo:
debug_print("Foo");
Hay un truco simple y anticuado:
debug_print("%s\n", "Foo");
La solución solo para GCC que se muestra a continuación también brinda soporte para eso.
Sin embargo, puede hacerlo con el sistema directo C99 usando:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
En comparación con la primera versión, pierde la comprobación limitada que requiere el argumento 'fmt', lo que significa que alguien podría intentar llamar a 'debug_print ()' sin argumentos (pero la coma final en la lista de argumentos fprintf()
no podría compilarse) . Es discutible si la pérdida de la verificación es un problema en absoluto.
Técnica específica de CCG para un solo argumento
Algunos compiladores pueden ofrecer extensiones para otras formas de manejar listas de argumentos de longitud variable en macros. Específicamente, como se señaló por primera vez en los comentarios de Hugo Ideler , GCC le permite omitir la coma que normalmente aparecería después del último argumento 'fijo' de la macro. También le permite usar ##__VA_ARGS__
en el texto de reemplazo de macro, que elimina la coma que precede a la notación si, pero solo si, el token anterior es una coma:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Esta solución conserva el beneficio de requerir el argumento de formato mientras acepta argumentos opcionales después del formato.
Esta técnica también es compatible con Clang para la compatibilidad con GCC.
¿Por qué el bucle do-while?
¿Cuál es el propósito de do while
aquí?
Desea poder usar la macro para que parezca una llamada a función, lo que significa que será seguida por un punto y coma. Por lo tanto, debe empaquetar el cuerpo macro para que se adapte. Si usa una if
declaración sin el entorno do { ... } while (0)
, tendrá:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Ahora, supongamos que escribe:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
Desafortunadamente, esa sangría no refleja el control real del flujo, porque el preprocesador produce un código equivalente a esto (sangría y llaves añadidas para enfatizar el significado real):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
El siguiente intento en la macro podría ser:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
Y el mismo fragmento de código ahora produce:
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);
Y else
ahora es un error de sintaxis. El do { ... } while(0)
bucle evita estos dos problemas.
Hay otra forma de escribir la macro que podría funcionar:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
Esto deja el fragmento de programa que se muestra como válido. La (void)
conversión evita que se use en contextos donde se requiere un valor, pero podría usarse como el operando izquierdo de un operador de coma donde la do { ... } while (0)
versión no puede. Si cree que debería poder incrustar código de depuración en tales expresiones, puede preferir esto. Si prefiere requerir que la impresión de depuración actúe como una declaración completa, entonces la do { ... } while (0)
versión es mejor. Tenga en cuenta que si el cuerpo de la macro involucra algún punto y coma (en términos generales), entonces solo puede usar la do { ... } while(0)
notación. Siempre funciona; El mecanismo de declaración de expresión puede ser más difícil de aplicar. También puede recibir advertencias del compilador con la forma de expresión que prefiere evitar; dependerá del compilador y las banderas que use.
TPOP estaba anteriormente en http://plan9.bell-labs.com/cm/cs/tpop y http://cm.bell-labs.com/cm/cs/tpop pero ambos están ahora (2015-08-10) roto.
Código en GitHub
Si tienes curiosidad, se puede ver en este código en GitHub en mis SOQ (Preguntas desbordamiento de pila) repositorio como archivos debug.c
, debug.h
y mddebug.c
en el
/ libsoq src
subdirectorio.