Intentando crear una macro que se pueda usar para imprimir mensajes de depuración cuando se defina DEBUG, como el siguiente pseudocódigo:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
¿Cómo se logra esto con una macro?
c
c-preprocessor
jfarrell
fuente
fuente
Respuestas:
Si usa un compilador C99 o posterior
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:
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: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:
Y luego, en el código, escriba:
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:
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)
debug.h - versión 3.6 (2008-02-11)
Variante de argumento único para C99 o posterior
Kyle Brandt preguntó:
Hay un truco simple y anticuado:
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:
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: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?
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 entornodo { ... } while (0)
, tendrá:Ahora, supongamos que escribe:
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):
El siguiente intento en la macro podría ser:
Y el mismo fragmento de código ahora produce:
Y
else
ahora es un error de sintaxis. Eldo { ... } while(0)
bucle evita estos dos problemas.Hay otra forma de escribir la macro que podría funcionar:
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 lado { ... } 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 lado { ... } 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 lado { ... } 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
ymddebug.c
en el / libsoq src subdirectorio.fuente
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
, no se compilará si no tiene parámetros de printf, es decir, si solo llamadebug_print("Some msg\n");
Puede arreglar esto usandofprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__ no permite pasar parámetros a la función.#define debug_print(fmt, ...)
y#define debug_print(...)
. El primero de estos requiere al menos un argumento, la cadena de formato (fmt
) y cero o más argumentos; el segundo requiere cero o más argumentos en total. Si usadebug_print()
el primero, recibirá un error del preprocesador sobre el mal uso de la macro, mientras que el segundo no. Sin embargo, aún obtiene errores de compilación porque el texto de reemplazo no es válido C. Por lo tanto, realmente no es una gran diferencia, de ahí el uso del término 'verificación limitada'.-D input=4,macros=9,rules=2
para establecer el nivel de depuración del sistema de entrada en 4, el sistema de macros en 9 (sometido a un escrutinio intenso ) y el sistema de reglas para 2. Hay infinitas variaciones sobre el tema; usa lo que te convenga.Yo uso algo como esto:
Entonces solo uso D como prefijo:
El compilador ve el código de depuración, no hay problemas de coma y funciona en todas partes. También funciona cuando
printf
no es suficiente, por ejemplo, cuando debe volcar una matriz o calcular algún valor de diagnóstico que sea redundante para el programa en sí.EDITAR: Ok, podría generar un problema cuando hay
else
algún lugar cercano que pueda ser interceptado por esta inyecciónif
. Esta es una versión que lo repasa:fuente
for(;0;)
, puede generar un problema cuando escribe algo comoD continue;
oD break;
.Para una implementación portátil (ISO C90), puede usar paréntesis dobles, como este;
o (hack, no lo recomendaría)
fuente
Aquí está la versión que uso:
fuente
Haría algo como
Creo que esto es más limpio.
fuente
assert()
Del stdlib funciona de la misma manera y que normalmente sólo re-uso laNDEBUG
macro para mi propio código de depuración ...De acuerdo con http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , debería haber un
##
antes__VA_ARGS__
.De lo contrario, una macro
#define dbg_print(format, ...) printf(format, __VA_ARGS__)
no se compilará el ejemplo siguiente:dbg_print("hello world");
.fuente
fuente
Esto es lo que uso:
Tiene el buen beneficio de manejar correctamente printf, incluso sin argumentos adicionales. En el caso de DBG == 0, incluso el compilador más tonto no tiene nada para masticar, por lo que no se genera ningún código.
fuente
Mi favorito de los siguientes es
var_dump
, que cuando se llama como:var_dump("%d", count);
produce resultados como:
patch.c:150:main(): count = 0
Crédito a @ "Jonathan Leffler". Todos están contentos con C89:
Código
fuente
Entonces, cuando uso gcc, me gusta:
Porque se puede insertar en el código.
Supongamos que intentas depurar
Luego puedes cambiarlo a:
Y puede obtener un análisis de qué expresión se evaluó a qué.
Está protegido contra el problema de la doble evaluación, pero la ausencia de gensyms lo deja abierto a colisiones de nombres.
Sin embargo, anida:
Así que creo que siempre y cuando evites usar g2rE3 como nombre de variable, estarás bien.
Ciertamente, lo he encontrado (y versiones aliadas para cadenas, y versiones para niveles de depuración, etc.) invaluables.
fuente
He estado pensando cómo hacer esto durante años, y finalmente encontré una solución. Sin embargo, no sabía que ya había otras soluciones aquí. Primero, a diferencia de la respuesta de Leffler , no veo su argumento de que las impresiones de depuración siempre deben compilarse. Prefiero no tener toneladas de código innecesario ejecutándose en mi proyecto, cuando no es necesario, en los casos en que necesito probar y es posible que no se optimicen.
No compilar cada vez puede sonar peor de lo que es en la práctica real. Terminas con impresiones de depuración que a veces no se compilan, pero no es tan difícil compilarlas y probarlas antes de finalizar un proyecto. Con este sistema, si está utilizando tres niveles de depuración, simplemente colóquelo en el nivel tres del mensaje de depuración, corrija sus errores de compilación y verifique si hay otros antes de finalizar su código. (Dado que, por supuesto, la compilación de declaraciones de depuración no garantiza que sigan funcionando según lo previsto).
Mi solución también proporciona niveles de detalle de depuración; y si lo configura al nivel más alto, todos se compilan. Si ha estado utilizando un alto nivel de detalle de depuración recientemente, todos pudieron compilar en ese momento. Las actualizaciones finales deberían ser bastante fáciles. Nunca he necesitado más de tres niveles, pero Jonathan dice que ha usado nueve. Este método (como el de Leffler) se puede extender a cualquier número de niveles. El uso de mi método puede ser más simple; requiere solo dos declaraciones cuando se usa en su código. Sin embargo, también estoy codificando la macro CLOSE, aunque no hace nada. Podría si estuviera enviando a un archivo.
Contra el costo, el paso adicional de probarlos para ver que compilarán antes de la entrega, es que
Las ramas son en realidad relativamente costosas en los modernos procesadores de precarga. Tal vez no sea un gran problema si su aplicación no es crítica; pero si el rendimiento es un problema, sí, un problema lo suficientemente grande como para preferir optar por un código de depuración de ejecución algo más rápida (y posiblemente una versión más rápida, en casos raros, como se señaló).
Entonces, lo que quería es una macro de impresión de depuración que no se compila si no se va a imprimir, pero lo hace si lo es. También quería niveles de depuración, de modo que, por ejemplo, si quisiera que partes del código cruciales para el rendimiento no se imprimieran en algunos momentos, sino que se imprimieran en otros, podría establecer un nivel de depuración y tener impresiones de depuración adicionales. Encontré una forma de implementar niveles de depuración que determinaban si la impresión se compilaba o no. Lo logré de esta manera:
DebugLog.h:
DebugLog.cpp:
Usando las macros
Para usarlo, solo haz:
Para escribir en el archivo de registro, simplemente haga:
Para cerrarlo, haces:
aunque actualmente esto ni siquiera es necesario, técnicamente hablando, ya que no hace nada. Todavía estoy usando CLOSE en este momento, sin embargo, en caso de que cambie de opinión sobre cómo funciona y quiero dejar el archivo abierto entre las declaraciones de registro.
Luego, cuando desee activar la impresión de depuración, simplemente edite el primer #define en el archivo de encabezado para decir, por ejemplo
Para que las instrucciones de registro se compilen en nada, haga
Si necesita información de un código ejecutado con frecuencia (es decir, un alto nivel de detalle), puede escribir:
Si define DEBUG como 3, los niveles de registro 1, 2 y 3 se compilan. Si lo configura en 2, obtiene los niveles de registro 1 y 2. Si lo establece en 1, solo obtiene declaraciones de nivel de registro 1.
En cuanto al ciclo do-while, dado que esto se evalúa como una sola función o nada, en lugar de una declaración if, el ciclo no es necesario. OK, castígame por usar C en lugar de C ++ IO (y QString :: arg () de Qt es una forma más segura de formatear variables cuando también en Qt: es bastante hábil, pero requiere más código y la documentación de formateo no está tan organizada como podría ser, pero todavía he encontrado casos en los que es preferible), pero puede poner cualquier código en el archivo .cpp que desee. También podría ser una clase, pero luego necesitarías crear una instancia y mantener el ritmo, o hacer un nuevo () y almacenarlo. De esta manera, simplemente suelta las declaraciones #include, init y opcionalmente close en su fuente, y está listo para comenzar a usarlo. Sin embargo, sería una buena clase si te inclinas tanto.
Anteriormente había visto muchas soluciones, pero ninguna se ajustaba a mis criterios tan bien como esta.
No es terriblemente significativo, pero además:
DEBUGLOG_LOG(3, "got here!");
); lo que le permite utilizar, por ejemplo, el formato .arg () más seguro de Qt. Funciona en MSVC y, por lo tanto, probablemente en gcc. Se usa##
en la#define
s, que no es estándar, como señala Leffler, pero es ampliamente compatible. (Puede recodificarlo para no usarlo##
si es necesario, pero tendrá que usar un truco como el que él proporciona).Advertencia: si olvida proporcionar el argumento de nivel de registro, MSVC inútilmente afirma que el identificador no está definido.
Es posible que desee utilizar un nombre de símbolo de preprocesador que no sea DEPURAR, ya que algunas fuentes también definen ese símbolo (por ejemplo, programas que utilizan
./configure
comandos para prepararse para la construcción). Me pareció natural cuando lo desarrollé. Lo desarrollé en una aplicación donde la DLL está siendo utilizada por otra cosa, y es más conveniente enviar impresiones de registro a un archivo; pero cambiarlo a vprintf () también funcionaría bien.Espero que esto les ahorre a muchos la pena por encontrar la mejor manera de hacer el registro de depuración; o te muestra uno que prefieras. A medias he estado tratando de resolver esto durante décadas. Funciona en MSVC 2012 y 2015, y por lo tanto probablemente en gcc; así como probablemente trabajando en muchos otros, pero no lo he probado en ellos.
Me refiero a hacer una versión de este día también.
Nota: Gracias a Leffler, quien cordialmente me ayudó a formatear mejor mi mensaje para StackOverflow.
fuente
if (DEBUG)
declaraciones en tiempo de ejecución, que no se optimizan", lo que se inclina en los molinos de viento . El punto completo del sistema que describí es que el compilador verifica el código (importante y automático, no se requiere una compilación especial) pero el código de depuración no se genera en absoluto porque está optimizado (por lo que no hay impacto en tiempo de ejecución en tamaño o rendimiento del código porque el código no está presente en tiempo de ejecución).((void)0)
- es fácil.Creo que esta variación del tema proporciona categorías de depuración sin la necesidad de tener un nombre de macro separado por categoría.
Utilicé esta variación en un proyecto Arduino donde el espacio del programa está limitado a 32K y la memoria dinámica está limitada a 2K. La adición de sentencias de depuración y cadenas de depuración de rastreo consume rápidamente espacio. Por lo tanto, es esencial poder limitar el seguimiento de depuración que se incluye en el momento de la compilación al mínimo necesario cada vez que se genera el código.
debug.h
llamando al archivo .cpp
fuente