DEPURAR macros en C++

16 minutos de lectura

Acabo de encontrar una macro DEBUG en C que realmente me gusta

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

Supongo que un análogo de C ++ sería: –

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. ¿Es el segundo fragmento de código análogo al de C?
  2. ¿Tiene alguna macro de depuración de C++ favorita?

EDITAR: Por “Depurar macros” me refiero a “macros que pueden ser útiles al ejecutar un programa en modo de depuración”.

  • Lea las preguntas frecuentes con respecto a su segunda pregunta…

    –Oliver Charlesworth

    10 de enero de 2013 a las 4:58

  • Algo no relacionado, pero esta podría ser una buena lectura.

    – Karthik T.

    10 de enero de 2013 a las 5:02

  • @OliCharlesworth Me doy cuenta de que mi segunda pregunta puede ser subjetiva; sin embargo, no estoy pidiendo una opinión sobre algo específico, estoy pidiendo ejemplos de código que son comúnmente utilizados por el lote de programación en SO. Sin embargo, si los visitantes aún sienten que la pregunta no es apropiada para SO, me aseguraré de editarla.

    usuario277465

    10 de enero de 2013 a las 5:09

  • @Pubby Disculpas por no haber sido claro: quise decir “macros que podrían ser útiles al ejecutar un programa en modo de depuración”

    usuario277465

    10 de enero de 2013 a las 5:12

  • me parece perfecto, funcionará

    -Kinjal Patel

    10 de enero de 2013 a las 5:13

DEPURAR macros en C
MvG

¿Es el segundo fragmento de código análogo al de C?

Más o menos. Es más poderoso, ya que puedes incluir <<-valores separados en el argumento, por lo que con un solo argumento se obtiene algo que requeriría un número variable de macroargumentos en C. Por otro lado, existe una pequeña posibilidad de que la gente abuse de él al incluir un punto y coma en el argumento. O incluso encuentre errores debido a un punto y coma olvidado después de la llamada. Así que incluiría esto en un bloque do:

#define DEBUG(x) do { std::cerr << x; } while (0)

¿Tiene alguna macro de depuración de C++ favorita?

Me gusta el de arriba y lo uso con bastante frecuencia. Mi no-op por lo general solo lee

#define DEBUG(x)

que tiene el mismo efecto para optimizar compiladores. Aunque el comentario de @Tony D a continuación es correcto: esto puede dejar algunos errores de sintaxis sin detectar.

A veces también incluyo una verificación en tiempo de ejecución, proporcionando así algún tipo de indicador de depuración. Como @Tony D me recordó, tener un endl allí también suele ser útil.

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

A veces también quiero imprimir la expresión:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

En algunas macros, me gusta incluir __FILE__, __LINE__ o __func__pero estas son más a menudo aserciones y no simples macros de depuración.

  • “Mi no-op por lo general solo lee #define DEBUG(x)“… a do-while (false) la sustitución tiende a producir un error si falta el punto y coma de la invocación DEBUG, en lugar de dejar que la siguiente declaración se deslice en el lugar de DEBUG. Por ejemplo, “if (expr) DEBUG”, siguiente línea: “++i;”, obtendría “if (expr) ++i;”. Razón pequeña para preferir el do-while. Muchas veces poniendo “<< '\n'” en la macro también es mejor.

    – Tony Delroy

    3 de junio de 2013 a las 4:54

  • @TonyD: Buenos puntos, actualicé mi respuesta. Aunque std::endl es superior a '\n' ya que también descarga la corriente.

    – MvG

    3 de junio de 2013 a las 6:17

  • En el caso de std::cerrnormalmente tiene un búfer de línea, por lo que se vaciará de todos modos y usando std::endl es baratamente redundante, pero, lo que me molesta, es que la gente use std::endl en operadores de transmisión que podrían usarse para std::coutun flujo de archivos, etc., que arruinan el almacenamiento en búfer y pueden conducir a un rendimiento dramáticamente peor.

    – Tony Delroy

    3 de junio de 2013 a las 9:47


  • El no-op utilizado por assert es ((void)0)

    – Jack Wasey

    25 mayo 2018 a las 14:35

DEPURAR macros en C
steven lu

aquí está mi favorito

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

Es muy útil y permite un código limpio (¡y lo que es más importante, rápido en el modo de lanzamiento!).

Un montón de #ifdef DEBUG_BUILD bloques por todas partes (para filtrar bloques de código relacionados con la depuración) es bastante feo, pero no tan malo cuando envuelves algunas líneas con un D().

Cómo utilizar:

D(cerr << "oopsie";)

Si todavía te parece demasiado feo/raro/largo,

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(sugiero no usar using namespace std; aunque tal vez using std::cout; using std::cerr; podría ser una buena idea)

Tenga en cuenta que es posible que desee hacer más cosas que simplemente imprimir en stderr cuando esté pensando en “depurar”. Sea creativo y podrá crear construcciones que ofrezcan información sobre las interacciones más complejas dentro de su programa, al mismo tiempo que le permite cambiar muy rápidamente a la creación de una versión supereficiente sin las trabas de la instrumentación de depuración.

Por ejemplo, en uno de mis proyectos recientes, tenía un gran bloque solo de depuración que comenzaba con FILE* file = fopen("debug_graph.dot"); y procedió a descargar un gráficoviz gráfico compatible en formato de puntos para visualizar grandes árboles dentro de mis estructuras de datos. Lo que es aún más genial es que el cliente OS X graphviz leerá automáticamente el archivo del disco cuando cambie, ¡así que el gráfico se actualiza cada vez que se ejecuta el programa!

También me gusta particularmente “extender” clases/estructuras con miembros y funciones solo de depuración. Esto abre la posibilidad de implementar la funcionalidad y el estado que está allí para ayudarlo a rastrear errores y, al igual que todo lo demás que está envuelto en macros de depuración, se elimina cambiando un parámetro de compilación. ¿Una rutina gigante que revisa minuciosamente cada caso de esquina en cada actualización de estado? No es un problema. abofetea a D() alrededor. Una vez que veas que funciona, elimina -DDEBUG desde el script de compilación, es decir, compilado para el lanzamiento, y se ha ido, listo para volver a habilitarse en cualquier momento para su prueba de unidad o lo que sea.

Un ejemplo grande, algo completo, para ilustrar (quizás un poco demasiado entusiasta) el uso de este concepto:

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

Tenga en cuenta que para grandes bloques de código, solo uso bloques regulares #ifdef condicionales porque eso mejora un poco la legibilidad, ya que para bloques grandes, ¡el uso de macros extremadamente cortas es más un obstáculo!

La razón por la cual el N(x) macro debe existir es para especificar qué agregar cuando la prueba unitaria es discapacitado.

En esta parte:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

Sería bueno si pudiéramos decir algo como

GraphNode(uint64_t i, U(Component* c,) int first_edge):

Pero no podemos, porque la coma es parte de la sintaxis del preprocesador. Omitir la coma produce una sintaxis de C++ no válida.

Si tuviera algún código adicional para cuando no compilando para la depuración, puede usar este tipo de macro de depuración inversa correspondiente.

Ahora bien, este código podría no ser un ejemplo de “código realmente bueno”, pero ilustra algunas de las cosas que puede lograr con la aplicación inteligente de macros, que si mantiene la disciplina, no son necesariamente mal.

Encontré esta gema justo ahora después de preguntarme sobre el do{} while(0) cosas, ¡y realmente quieres toda esa fantasía en estas macros también!

Con suerte, mi ejemplo puede proporcionar una idea de al menos algunas de las cosas inteligentes que se pueden hacer para mejorar su código C++. Es realmente valioso instrumentar el código mientras lo escribes en lugar de volver a hacerlo cuando no entiendes lo que está sucediendo. Pero siempre es un equilibrio que debe lograr entre hacerlo sólido y hacerlo a tiempo.

Me gusta pensar en verificaciones de cordura de compilación de depuración adicionales como una herramienta diferente en la caja de herramientas, similar a las pruebas unitarias. En mi opinión, podrían ser incluso más potentes, porque en lugar de poner la lógica de comprobación de cordura en las pruebas unitarias y aislarlas de la implementación, si se incluyen en la implementación y se pueden conjurar a voluntad, entonces las pruebas completas no son tan necesarias. porque simplemente puede habilitar los controles y ejecutar las cosas como de costumbre, en un abrir y cerrar de ojos.

  • Wow, nunca he conocido ese parámetro de macro (x en D(x)) también puede comer << y "". ¿Hasta dónde se puede interpretar? Escuché que el parámetro de macro no puede contener caracteres ( o ). ¿Puede proporcionar alguna regla/referencia de limitación/enlace al respecto, por favor? Es difícil encontrar tal regla en los libros. Gracias.

    – cppPrincipiante

    21 de enero de 2019 a las 6:36


  • AFAIK, la forma en que funciona una macro es que sustituye la cadena hasta que la sintaxis evita que lo haga. Debido a que los elementos primarios están implicados en la sustitución, por ejemplo, no podrá incrustar un conjunto de elementos primarios que no coincida. Y las comas también están implicadas (¡en la separación de los macro argumentos!). Pero todo lo demás es juego limpio. Emplee los trucos con moderación y responsabilidad…

    – Steven Lu

    21 de enero de 2019 a las 10:28


  • Nunca uses macros vacías como # define D(x), son muy peligrosos! como se ha señalado en los comentarios a la respuesta aceptada (que también sufre de este problema), obtienes un comportamiento inesperado si olvidas por error el punto y coma después, como if (x) D(x) i++; donde i++ se ejecuta solo si x es distinto de cero, mientras que parece ejecutado cada vez. si lo pones como # define D(x) do { } while(0) el compilador aún lo elimina sin problemas, pero producirá un error de compilación en caso de que falte un punto y coma.

    –Marcin Tarsier

    31 de octubre de 2019 a las 13:08

  • Tienes razón @MarcinTarsier. Lo agregaré a mis listados de códigos para hacerlos más seguros. Es una pena que ahora sea un poco más feo.

    – Steven Lu

    27 de enero de 2020 a las 21:14


  • Esto no funcionará en una definición de estructura. struct { D(int prueba_de_inicialización;) … }. Después de varios intentos con la depuración activada y desactivada y moviendo el punto y coma, finalmente opté por el simple y peligroso #define D(x) x y #define D(x). Esto tiene sus problemas, como se señaló, pero en el lado positivo, realmente funciona.

    – Samuel Danielson

    20/09/2021 a las 13:33

Para la pregunta 1]La respuesta es sí. Simplemente imprimirá el mensaje en el flujo de error estándar.

Para la pregunta 2]Hay muchos. mi favorito es

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

lo que permitirá incluir un número arbitrario de variables para incluir en el mensaje de depuración.

  • ¿Es posible incluir el carácter de nueva línea (\n) en la propia macro?

    –Saurabh Shrivastava

    30 de julio de 2017 a las 13:53

DEPURAR macros en C
esteras petersson

Me gusta usar macros con __LINE__, __FILE__ como argumentos para demostrar donde en el código de donde proviene la impresión; no es raro imprimir el mismo nombre de variable en varios lugares, por lo que fprintf(stderr, "x=%d", x); no significará mucho si luego agrega otro diez líneas más abajo.

También he usado macros que anulan ciertas funciones y almacenan desde dónde se llamó (por ejemplo, asignaciones de memoria), para que luego pueda averiguar cuál fue la que se filtró. Para la asignación de memoria, eso es un poco más difícil en C ++, ya que tiende a usar nuevo/eliminar, y no se pueden reemplazar fácilmente, pero otros recursos, como las operaciones de bloqueo/desbloqueo, pueden ser muy útiles para rastrear de esta manera. [of course, if you have a locking wrapper that uses construction/destruction like a good C++ programmer, you’d add it to the constructor to add file/line to the internal structure once you have acquired the lock, and you can see where it’s held elsewhere when the you can’t acquire it somewhere].

1646756656 645 DEPURAR macros en C
firestoke

Esta es la macro de registro que estoy usando actualmente:

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

Uso:

log(">>> test...");

Producción:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

  • Observación: como se muestra actualmente, siempre tiene la depuración habilitada. También limita la salida a 100 caracteres (que no es muy largo), pero no garantiza que no se desborde el búfer al usar snprintf() en lugar de sprintf(). Eso es vivir peligrosamente, ¿no? ¿No debería ir su registro a std::cerr o std::clog? ¿Qué tiene de nuevo y claramente diferente esta respuesta en comparación con otras?

    –Jonathan Leffler

    1 de julio de 2015 a las 4:49

1646756657 64 DEPURAR macros en C
zaufí

… y como anexo a todas las respuestas:

Personalmente, nunca uso macros como DEBUG para distinguir la depuración del código de lanzamiento, en su lugar uso NDEBUG cual es debe ser definido para compilaciones de lanzamiento para eliminar assert() llamadas (sí, uso assert() extensamente). Y si este último no está definido, entonces es una compilación de depuración. ¡Fácil! Entonces, en realidad no hay razón para introducir una macro de depuración más. (y manejar posibles casos cuando DEBUG y NDEBUG ambos no están definidos).

  • Observación: como se muestra actualmente, siempre tiene la depuración habilitada. También limita la salida a 100 caracteres (que no es muy largo), pero no garantiza que no se desborde el búfer al usar snprintf() en lugar de sprintf(). Eso es vivir peligrosamente, ¿no? ¿No debería ir su registro a std::cerr o std::clog? ¿Qué tiene de nuevo y claramente diferente esta respuesta en comparación con otras?

    –Jonathan Leffler

    1 de julio de 2015 a las 4:49

1646756658 457 DEPURAR macros en C
rubenvb

Esta es mi versión, usando una plantilla variada. print función:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

La versión que hago debug_print una función de plantilla variable que acepta un nivel de depuración que me permite seleccionar qué tipo de salida quiero generar en tiempo de ejecución:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

Nota la print la función bloquea Visual Studio 2013 Preview (no he probado el RC). He notado que es más rápido (en Windows, donde la salida de la consola es lenta) que mi solución anterior que usaba un ostream clase infantil que se sobrecargó operator<<.

También puede utilizar un temporal stringstream en el interior print si solo desea llamar a la función de salida real una vez (o escribir su propio typesafe printf ;-))

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad