Question

Je m'interroge sur l'utilisation pratique de #undef en C. Je travaille avec K & et R, et je suis à la hauteur du préprocesseur. C’était en grande partie du matériel que j’avais (plus ou moins) compris, mais quelque chose à la page 90 (deuxième édition) m’a percé:

  

Les noms peuvent être indéfinis avec #undef,   généralement pour assurer qu'une routine est   vraiment une fonction, pas une macro:

     

#undef getchar

     

int getchar(void) { ... }

S'agit-il d'une pratique courante pour se défendre contre quelqu'un #define - d'une macro portant le même nom que votre fonction? Ou s'agit-il vraiment d'un échantillon qui ne se produirait pas dans la réalité? (Par exemple, personne dans son esprit juste, mauvais ou insensé ne devrait réécrire getchar(), il ne devrait donc pas apparaître). Avec vos propres noms de fonction, sentez-vous le besoin de le faire? Est-ce que cela change si vous développez une bibliothèque que d'autres pourront utiliser?

Était-ce utile?

La solution

Ce qu'il fait

Si vous lisez la bibliothèque Standard C de Plauger (1992), vous verrez que le <stdio.h> en-tête est autorisé à fournir getchar() et getc() sous forme de macros de type fonction (avec une permission spéciale pour core_function() d'évaluer son argument de pointeur de fichier plusieurs fois!). Cependant, même si elle fournit des macros, l’implémentation est également obligée de fournir les fonctions effectives qui font le même travail, principalement pour que vous puissiez accéder à un pointeur de fonction appelé isxxxx() ou <ctype.h> et le transmettre à d’autres fonctions.

C’est-à-dire en faisant:

#include <stdio.h>
#undef getchar

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

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

Tel qu'écrit, le #undef n'a pas beaucoup de sens, mais il illustre bien ce point. Vous pouvez également faire de même avec les getchar macros dans (, par exemple.

Normalement, vous ne voulez pas faire cela - vous ne voulez normalement pas supprimer la définition de macro. Mais, lorsque vous avez besoin de la fonction réelle, vous pouvez la récupérer. Les personnes qui fournissent des bibliothèques peuvent très bien émuler les fonctionnalités de la bibliothèque standard C.

Rarement nécessaire

Notez également que l’une des raisons pour lesquelles vous avez rarement besoin d’utiliser le function explicite est que vous pouvez appeler la fonction à la place de la macro en écrivant:

int c = (getchar)();

Comme le jeton après return n'est pas un <=>, il ne s'agit pas d'un appel de la macro semblable à une fonction. Il doit donc s'agir d'une référence à la fonction. De même, le premier exemple ci-dessus, compilerait et fonctionnerait correctement même sans <=>.

Si vous implémentez votre propre fonction avec une substitution de macro, vous pouvez l'utiliser à bon escient, bien que cela puisse être légèrement déroutant, sauf indication contraire.

/* 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 ligne de définition de fonction n'invoque pas la macro car le jeton après <=> n'est pas <=>. La ligne <=> appelle la macro.

Autres conseils

Les macros sont souvent utilisées pour générer une grande partie du code. C’est souvent une utilisation assez localisée et #undef toutes les macros d’aide à la fin de l’en-tête sont sécurisées afin d’éviter les conflits de noms. Ainsi, seul le code généré est importé ailleurs et les macros utilisées pour le générer ne le sont pas.

/ Edit: Par exemple, j'ai utilisé cela pour générer des structs pour moi. Ce qui suit est un extrait d’un projet réel:

#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

Les pré-processeurs #define étant tous situés dans un seul espace de noms global, il est facile d’entraîner des conflits d’espace de noms, en particulier lors de l’utilisation de bibliothèques tierces. Par exemple, si vous souhaitez créer une fonction nommée OpenFile, elle risque de ne pas être compilée correctement car le fichier d’en-tête <windows.h> définit le jeton OpenFileA à mapper sur OpenFileW ou UNICODE (selon que < => est défini ou non). La bonne solution consiste à #undef <=> avant de définir votre fonction.

Je l'utilise uniquement lorsqu'une macro dans un fichier #included interfère avec l'une de mes fonctions (par exemple, elle porte le même nom). Ensuite, je #undef la macro pour pouvoir utiliser ma propre fonction.

Bien que je pense que Jonathan Leffler vous ait donné la bonne réponse. Voici un cas très rare, où j'utilise un #undef. Normalement, une macro doit être réutilisable dans de nombreuses fonctions. c'est pourquoi vous le définissez en haut d'un fichier ou dans un fichier d'en-tête. Mais parfois, vous avez du code répétitif dans une fonction qui peut être raccourcie avec une 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;
}

Pour montrer au lecteur que cette macro n’est utile que dans la fonction, elle est indéfinie à la fin. Je ne veux encourager personne à utiliser de telles macros hackish. Mais si vous devez, #undef à la fin.

  

Est-ce une pratique courante de se défendre contre quelqu'un # qui définit une macro portant le même nom que votre fonction? Ou s'agit-il vraiment d'un échantillon qui ne se produirait pas dans la réalité? (Par exemple, personne dans son esprit juste, mauvais ou insensé ne devrait réécrire getchar (), il ne devrait donc pas apparaître.)

Un peu des deux. Un bon code ne nécessitera pas l'utilisation de #undef, mais il y a beaucoup de mauvais codes avec lesquels vous devez travailler. #define bool int peut s'avérer inestimable quand quelqu'un tire un tour comme <=>.

Outre la résolution des problèmes de macros qui polluent l’espace de noms global, #undef permet également d’exiger qu'une macro ait un comportement différent à des endroits différents. Ce n’est pas un scénario très courant, mais un couple qui me vient à l’esprit est:

  • La macro assert peut avoir sa définition modifiée au milieu d'une unité de compilation, dans le cas où vous voudriez peut-être effectuer le débogage sur une partie de votre code mais pas sur d'autres. En plus de NDEBUG lui-même devant être extern 'éd, la macro <=> doit être redéfinie pour reconfigurer le comportement souhaité de <=>

  • J'ai vu une technique utilisée pour garantir que les globales sont définis exactement une fois en utilisant une macro pour déclarer les variables comme étant <=>, mais la macro serait redéfinie sur rien dans le seul cas où l'en-tête / les déclarations sont utilisées pour définir les variables.

Quelque chose comme (je ne dis pas que c'est nécessairement une bonne technique, juste une que j'ai vue à l'état sauvage):

/* 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 une macro peut être définie, il doit y avoir une installation pour undef.

un suiveur de mémoire que j'utilise définit ses propres macros nouvelles / supprimer pour suivre les informations de fichier / ligne. cette macro casse le SC ++ L.

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

Concernant votre question plus spécifique: les espaces de noms sont souvent émulés en C en préfixant les fonctions de bibliothèque avec un identifiant.

Les macros masquées aveuglément vont créer de la confusion, réduire la maintenabilité et risquent de casser des choses qui reposent sur le comportement d'origine. Si vous y êtes forcé, utilisez au moins push / pop pour conserver le comportement d'origine partout ailleurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top