Pregunta

Me pregunto sobre el uso práctico de #undef en C.Estoy trabajando con K&R y estoy en el preprocesador.La mayor parte de esto era material que (más o menos) entendí, pero algo en la página 90 (segunda edición) me llamó la atención:

Los nombres pueden no estar definidos con #undef, generalmente para asegurar que una rutina sea realmente una función, no una macro:

#undef getchar

int getchar(void) { ... }

¿Es esta una práctica común para defenderse de alguien? #define-¿Tienes una macro con el mismo nombre que tu función?¿O se trata realmente de una muestra que no ocurriría en la realidad?(EG, nadie en su mente correcta, incorrecta o loca debería reescribir getchar(), por lo que no debería aparecer). Con sus propios nombres de funciones, ¿siente la necesidad de hacer esto?¿Eso cambia si estás desarrollando una biblioteca para que otros la usen?

¿Fue útil?

Solución

Que hace

Si lees el libro de Plauger La biblioteca C estándar (1992), verás que el <stdio.h> el encabezado puede proporcionar getchar() y getc() como macros similares a funciones (con permiso especial para getc() evaluar su argumento de puntero de archivo más de una vez).Sin embargo, incluso si proporciona macros, la implementación también está obligada a proporcionar funciones reales que hagan el mismo trabajo, principalmente para que pueda acceder a un puntero de función llamado getchar() o getc() y pasar eso a otras funciones.

Es decir, haciendo:

#include <stdio.h>
#undef getchar

extern int some_function(int (*)(void));

int core_function(void)
{
   int c = some_function(getchar);
   return(c);
}

Como está escrito, el core_function() No tiene mucho sentido, pero ilustra el punto.Puedes hacer lo mismo con el isxxxx() macros en <ctype.h> también, por ejemplo.

Normalmente, no querrás hacer eso; normalmente no querrás eliminar la definición de macro.Pero, cuando necesites la función real, puedes conseguirla.Las personas que proporcionan bibliotecas pueden emular la funcionalidad de la biblioteca C estándar con buenos resultados.

Rara vez es necesario

También tenga en cuenta que una de las razones por las que rara vez necesita utilizar el método explícito #undef es porque puedes invocar la función en lugar de la macro escribiendo:

int c = (getchar)();

Porque la ficha después getchar no es un (, no es una invocación de la macro similar a una función, por lo que debe ser una referencia a la función.De manera similar, el primer ejemplo anterior se compilaría y ejecutaría correctamente incluso sin el #undef.

Si implementa su propia función con una anulación de macro, puede usar esto con buenos resultados, aunque puede resultar un poco confuso a menos que se explique.

/* function.h */
…
extern int function(int c);
extern int other_function(int c, FILE *fp);
#define function(c) other_function(c, stdout);
…
/* function.c */

…

/* Provide function despite macro override */
int (function)(int c)
{
    return function(c, stdout);
}

La línea de definición de función no invoca la macro porque el token después function no es (.El return La línea invoca la macro.

Otros consejos

Las macros se usan a menudo para generar gran cantidad de código. A menudo es un uso bastante localizado y es seguro #undef cualquier macros auxiliares al final del encabezado particular para evitar conflictos de nombres, por lo que solo el código generado real se importa a otra parte y las macros utilizadas para generar el código no.

/ Editar: como ejemplo, he usado esto para generar estructuras para mí. Lo siguiente es un extracto de un proyecto real:

#define MYLIB_MAKE_PC_PROVIDER(name) \
    struct PcApi##name { \
        many members …
    };

MYLIB_MAKE_PC_PROVIDER(SA)
MYLIB_MAKE_PC_PROVIDER(SSA)
MYLIB_MAKE_PC_PROVIDER(AF)

#undef MYLIB_MAKE_PC_PROVIDER

Debido a que los preprocesadores #define están todos en un espacio de nombres global, es fácil que surjan conflictos de espacio de nombres, especialmente cuando se usan bibliotecas de terceros. Por ejemplo, si desea crear una función llamada OpenFile, es posible que no se compile correctamente, porque el archivo de encabezado <windows.h> define el token OpenFileA para asignar a OpenFileW o UNICODE (dependiendo de si < => está definido o no). La solución correcta es #undef <=> antes de definir su función.

Solo lo uso cuando una macro en un archivo #included está interfiriendo con una de mis funciones (por ejemplo, tiene el mismo nombre). Entonces #undef la macro para poder usar mi propia función.

Aunque creo que Jonathan Leffler te dio la respuesta correcta. Aquí hay un caso muy raro, donde uso un #undef. Normalmente una macro debería ser reutilizable dentro de muchas funciones; es por eso que lo define en la parte superior de un archivo o en un archivo de encabezado. Pero a veces tiene un código repetitivo dentro de una función que se puede acortar con una macro.


int foo(int x, int y)
{
#define OUT_OF_RANGE(v, vlower, vupper) \
    if (v < vlower) {v = vlower; goto EXIT;} \
    else if (v > vupper) {v = vupper; goto EXIT;}

    /* do some calcs */
    x += (x + y)/2;
    OUT_OF_RANGE(x, 0, 100);
    y += (x - y)/2;
    OUT_OF_RANGE(y, -10, 50);

    /* do some more calcs and range checks*/
    ...

EXIT:
    /* undefine OUT_OF_RANGE, because we don't need it anymore */
#undef OUT_OF_RANGE
    ...
    return x;
}

Para mostrar al lector que esta macro solo es útil dentro de la función, no está definida al final. No quiero alentar a nadie a usar macros tan hackeadas. Pero si es necesario, #undef al final.

  

¿Es una práctica común defenderse contra alguien # definiendo una macro con el mismo nombre que su función? ¿O es realmente más una muestra que no ocurriría en la realidad? (Por ejemplo, nadie en su mente correcta, equivocada ni loca debería reescribir getchar (), por lo que no debería aparecer).

Un poco de ambos. Un buen código no requerirá el uso de #undef, pero hay muchos códigos malos con los que tienes que trabajar. #define bool int puede resultar invaluable cuando alguien hace un truco como <=>.

Además de solucionar problemas con macros que contaminan el espacio de nombres global, otro uso de #undef es la situación en la que se podría requerir que una macro tenga un comportamiento diferente en diferentes lugares. Este no es un escenario realmente común, pero una pareja que viene a la mente es:

  • la macro assert puede cambiar su definición en el medio de una unidad de compilación para el caso en que desee realizar una depuración en una parte de su código pero no en otras. Además de que NDEBUG necesita ser extern 'editado para hacer esto, la macro <=> necesita ser redefinida para reconfigurar el comportamiento deseado de <=>

  • He visto una técnica utilizada para garantizar que los globales se definan exactamente una vez mediante el uso de una macro para declarar las variables como <=>, pero la macro se redefiniría en nada para el caso único en el que el encabezado / las declaraciones se utilizan para definir las variables.

Algo así (no digo que sea necesariamente una buena técnica, solo una que he visto en la naturaleza):

/* globals.h */
/* ------------------------------------------------------ */
#undef GLOBAL
#ifdef DEFINE_GLOBALS
#define GLOBAL
#else
#define GLOBAL extern
#endif

GLOBAL int g_x;
GLOBAL char* g_name;
/* ------------------------------------------------------ */



/* globals.c */
/* ------------------------------------------------------ */
#include "some_master_header_that_happens_to_include_globals.h"

/* define the globals here (and only here) using globals.h */
#define DEFINE_GLOBALS
#include "globals.h"

/* ------------------------------------------------------ */

Si una macro se puede definir, debe haber una facilidad para undef.

un rastreador de memoria que uso define sus propias macros nuevas / eliminar para rastrear información de archivo / línea. Esta macro rompe el SC ++ L.

#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )

Con respecto a su pregunta más específica: los espacios de nombres a menudo se emulan; C en C prefijando las funciones de la biblioteca con un identificador.

Las macros indefinidamente ciegas agregarán confusión, reducirán la capacidad de mantenimiento y pueden romper cosas que dependen del comportamiento original. Si fue forzado, al menos use push / pop para preservar el comportamiento original en cualquier otro lugar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top