#define macro para la impresión de depuración en C?


209

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?


¿El compilador (gcc) optimizará sentencias como si (DEBUG) {...} fuera, si en el código de producción la macro DEBUG se establece en 0? Entiendo que hay buenas razones para dejar las declaraciones de depuración visibles para el compilador, pero persiste un mal presentimiento. -Pat
Pat

Respuestas:


410

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_printaú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 whileaquí?

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 ifdeclaració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 elseahora 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.hy mddebug.cen el / libsoq src subdirectorio.


1
Creo que el enfoque de GCC ## - de gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html sería digno de mencionar bajo el encabezado "Variante C99 de argumento único".
Hugo Ideler

2
Años más tarde, y esta respuesta sigue siendo la más útil de todas las Internet, sobre cómo alias printk! vfprintf no funciona en el espacio del núcleo ya que stdio no está disponible. ¡Gracias! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

66
En su ejemplo con las palabras clave __FILE__, __LINE__, __func__, __VA_ARGS__, no se compilará si no tiene parámetros de printf, es decir, si solo llama debug_print("Some msg\n"); Puede arreglar esto usando fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ no permite pasar parámetros a la función.
mc_electron

1
@LogicTom: la diferencia es entre #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 usa debug_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'.
Jonathan Leffler

1
La variante que se muestra arriba, @ St.Antario, usa un solo nivel de depuración activa en toda la aplicación, y usualmente uso las opciones de línea de comando para permitir que el nivel de depuración se establezca cuando se ejecuta el programa. También tengo una variante que reconoce múltiples subsistemas diferentes, cada uno de los cuales recibe un nombre y su propio nivel de depuración, de modo que puedo usar -D input=4,macros=9,rules=2para 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.
Jonathan Leffler

28

Yo uso algo como esto:

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

Entonces solo uso D como prefijo:

D printf("x=%0.3f\n",x);

El compilador ve el código de depuración, no hay problemas de coma y funciona en todas partes. También funciona cuando printfno 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 elsealgún lugar cercano que pueda ser interceptado por esta inyección if. Esta es una versión que lo repasa:

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

3
En cuanto a for(;0;), puede generar un problema cuando escribe algo como D continue;o D break;.
ACcreator

1
Me consiguió; Sin embargo, parece muy poco probable que pueda ocurrir por accidente.
mbq

11

Para una implementación portátil (ISO C90), puede usar paréntesis dobles, 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;
}

o (hack, no lo recomendaría)

#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 hacer que el preprocesador 'piense', solo hay un argumento, mientras que permite que _ se expanda en una etapa posterior.
Marcin Koziuk

10

Aquí está la versión que 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

Haría algo como

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

Creo que esto es más limpio.


Realmente no me gusta la idea de usar una macro dentro de una prueba como bandera. ¿Podría explicar por qué la impresión de depuración siempre debe verificarse?
LB40

1
@ Jonathan: Si el código solo se ejecuta en modo de depuración, ¿por qué debería importarle si se compila en modo sin depuración? assert()Del stdlib funciona de la misma manera y que normalmente sólo re-uso la NDEBUGmacro para mi propio código de depuración ...
Christoph

usando DEBUG en la prueba, si alguien hace un DEBUG no controlado, su código ya no se compila. Derecha ?
LB40

44
Es frustrante habilitar la depuración y luego tener que depurar el código de depuración porque se refiere a variables que han sido renombradas o reescritas, etc. Si el compilador (postprocesador) siempre ve la declaración de impresión, se asegura de que los cambios circundantes tengan No invalida 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 - plan9.bell-labs.com/cm/cs/tpop .
Jonathan Leffler

1
@ Christoph: bueno, más o menos ... Utilizo NDEBUG para controlar solo las aserciones, y una macro separada (generalmente DEPURACIÓN) para controlar el seguimiento de depuración. 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 a fprintf () directamente, llamo a una función de impresión de depuración que solo imprime condicionalmente la misma compilación del el código se puede imprimir o no según las opciones del programa). Estoy abogando por que para todas las compilaciones, el compilador debería ver las declaraciones de diagnóstico; sin embargo, no generará código a menos que la depuración esté habilitada.
Jonathan Leffler

8

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");.


1
Bienvenido a Stack Overflow. Tiene razón en que GCC tiene la extensión no estándar a la que hace referencia. De hecho, la respuesta actualmente aceptada menciona esto, incluida exactamente la URL de referencia que proporciona.
Jonathan Leffler

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

¿Qué versión de C admite esa notación? Y, si funcionó, el token que pega todos los argumentos de esa manera significa que solo tiene un conjunto muy limitado de opciones para la cadena de formato, ¿no?
Jonathan Leffler

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

1
OK - de acuerdo: está documentado como una antigua extensión GNU (sección 5.17 del manual GCC 4.4.1). Pero probablemente debería documentar que solo funcionará con GCC, o tal vez lo hemos hecho entre nosotros en estos comentarios.
Jonathan Leffler

1
Mi intención era mostrar otro estilo de usar args y principalmente para demostrar el uso de FUNCTION y LINE
eyalm

2

Esto es lo que uso:

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

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.


Es mejor que el compilador siempre verifique el código de depuración.
Jonathan Leffler

1

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

#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

Entonces, cuando uso gcc, me gusta:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

Porque se puede insertar en el código.

Supongamos que intentas depurar

printf("%i\n", (1*2*3*4*5*6));

720

Luego puedes cambiarlo a:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

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:

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

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.


1

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

  1. Debe confiar en ellos para que se optimicen, lo que ciertamente DEBE suceder si tiene un nivel de optimización suficiente.
  2. Además, probablemente no lo harán si realiza una compilación de lanzamiento con la optimización desactivada para fines de prueba (lo cual es ciertamente raro); y es casi seguro que no lo harán durante la depuración, ejecutando así docenas o cientos de declaraciones "if (DEBUG)" en tiempo de ejecución; ralentizando así la ejecución (que es mi principal objeción) y, lo que es menos importante, aumentando el tamaño de su ejecutable o dll; y, por lo tanto, tiempos de ejecución y compilación. Jonathan, sin embargo, me informa que su método puede hacerse para no compilar declaraciones en absoluto.

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:

// 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 las macros

Para usarlo, solo haz:

DEBUGLOG_INIT("afile.log");

Para escribir en el archivo de registro, simplemente haga:

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

Para cerrarlo, haces:

DEBUGLOG_CLOSE();

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

#define DEBUG 1

Para que las instrucciones de registro se compilen en nada, haga

#define DEBUG 0

Si necesita información de un código ejecutado con frecuencia (es decir, un alto nivel de detalle), puede escribir:

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

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.

  1. Se puede ampliar para hacer tantos niveles como desee.
  2. Se compila en nada si no se imprime.
  3. Centraliza IO en un lugar fácil de editar.
  4. Es flexible, utiliza el formato printf.
  5. Nuevamente, no ralentiza las ejecuciones de depuración, mientras que las impresiones de depuración siempre compiladas siempre se ejecutan en modo de depuración. Si está haciendo informática, y no es más fácil escribir el procesamiento de información, puede encontrarse ejecutando un simulador que consume CPU, para ver, por ejemplo, dónde el depurador lo detiene con un índice fuera de rango para un vector. Estos se ejecutan muy lentamente en modo de depuración ya. La ejecución obligatoria de cientos de impresiones de depuración necesariamente retrasará aún más tales ejecuciones. Para mí, tales carreras no son infrecuentes.

No es terriblemente significativo, pero además:

  1. No requiere ningún truco para imprimir sin argumentos (por ejemplo 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 #defines, 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 ./configurecomandos 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.


2
Usted dice "ejecutar docenas o cientos de 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).
Jonathan Leffler

Jonathan Leffler: Gracias por señalar mi mala redacción. Dejé que mis pensamientos corrieran más rápido que mis dedos, estando tan contento de haber terminado esto. He revisado mis objeciones con "... 1) debe confiar en ellos para que se optimicen, lo que ciertamente debería suceder si tiene un nivel de optimización suficiente. 2) Además, no lo harán si realiza una compilación de lanzamiento con optimización desactivado para fines de prueba, y probablemente no lo harán durante la depuración, ejecutando así docenas o cientos de declaraciones 'if (DEBUG)' en tiempo de ejecución, aumentando así el tamaño de su ejecutable o dll, y los tiempos de ejecución ".
CodeLurker el

Para que el tuyo haga otra cosa importante que está haciendo la mía, deberías tener niveles de depuración. Si bien a menudo no necesito activar muchos de ellos, algunas aplicaciones realmente se benefician de poder obtener un gran nivel de detalle sobre un ciclo de tiempo crítico con un simple "#define DEBUG 3", y luego volver a información mucho menos detallada con "#define DEBUG 1". Nunca he necesitado más de tres niveles y, por lo tanto, al menos aproximadamente 1/3 de mis depuraciones ya se compilan en el lanzamiento. Si he usado el nivel 3 recientemente, probablemente TODOS lo hagan.
CodeLurker el

YMMV. El sistema moderno que mostré admite la configuración dinámica (tiempo de ejecución) de los niveles de depuración, por lo que puede decidir mediante programación cuánto de la depuración se produce en tiempo de ejecución. Por lo general, utilizo los niveles 1-9, aunque no hay un límite superior (o límite inferior; el nivel predeterminado es 0, que generalmente está desactivado, pero se puede solicitar explícitamente durante el desarrollo activo si es apropiado; no es apropiado para el trabajo a largo plazo). Elegí un nivel predeterminado de 3; Las cosas se pueden ajustar. Esto me da mucho control. Si realmente no desea probar el código de depuración cuando está inactivo, cambie la alternativa a ((void)0)- es fácil.
Jonathan Leffler el

1
Ahh Hubiera ayudado haber leído todo. Es una publicación bastante larga. Creo que eso tiene los puntos esenciales hasta ahora. Resulta que el tuyo, como el mío, puede usarse para compilar o no compilar todas las impresiones de depuración, y puede admitir niveles; aunque es cierto, el tuyo puede compilar niveles que no estás utilizando, a un costo durante la depuración.
CodeLurker

0

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

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

llamando al archivo .cpp

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.