Frage
Ich frage mich, über den praktischen Einsatz von #undef in C. Ich arbeite durch K & R, und bin an den Präprozessor auf. Die meisten davon war Material, das ich (mehr oder weniger) zu verstehen, sondern etwas, auf Seite 90 (zweite Ausgabe) fest an mich:
Namen mit
#undef
nicht definiert werden können, in der Regel, um sicherzustellen, dass eine Routine wirklich eine Funktion, kein Makro:
#undef getchar
int getchar(void) { ... }
Ist dies eine gängige Praxis zu verteidigen gegen jemand ein Makro mit dem gleichen Namen wie die Funktion #define
-ing? Oder ist das wirklich mehr von einer Probe, die in der Realität nicht auftreten würde? (EG, niemand in seinen Rechten, falsch noch verrückt Geist getchar()
Umschreiben werden soll, damit es nicht kommen sollte, auf.) Mit dem eigenen Funktionsnamen, fühlen Sie die Notwendigkeit, dies zu tun? Wird diese Änderung, wenn Sie eine Bibliothek sind zu entwickeln, für andere zu benutzen?
Lösung
Was sie tut,
Wenn Sie Plauger Die Standard-C-Bibliothek (1992) lesen, sehen Sie, dass die <stdio.h>
Header getchar()
und getc()
als Funktionsmakros zur Verfügung zu stellen erlaubt (mit Sondergenehmigung für getc()
sein Dateizeigerargument mehr zu bewerten als einmal!). Doch selbst wenn es Makros bietet, ist die Umsetzung auch verpflichtet, die tatsächlichen Funktionen anbietest, die die gleiche Arbeit tun, in erster Linie, so dass Sie einen Funktionszeiger zugreifen können getchar()
oder getc()
und passieren, dass zu anderen Funktionen aufgerufen.
Das heißt, indem Sie:
#include <stdio.h>
#undef getchar
extern int some_function(int (*)(void));
int core_function(void)
{
int c = some_function(getchar);
return(c);
}
Wie schon geschrieben, die core_function()
ziemlich sinnlos ist, aber es zeigt den Punkt. Sie können mit dem isxxxx()
Makros in <ctype.h>
das gleiche tun auch, zum Beispiel.
Normalerweise wollen Sie nicht, das zu tun - Sie normalerweise nicht die Makrodefinition entfernen möchten. Aber, wenn Sie die reale Funktion benötigen, können Sie halten, es zu bekommen. Personen, die Bibliotheken bieten die Funktionalität der Standard-C-Bibliothek, um eine gute Wirkung emulieren kann.
Nur selten benötigt
Beachten Sie auch, dass einer der Gründe, benötigen Sie selten die explizite #undef
zu verwenden ist, weil Sie die Funktion anstelle des Makros durch Schreiben aufrufen kann:
int c = (getchar)();
Da die Token nach getchar
kein (
ist, ist es nicht ein Aufruf der funktionsähnlichen Makros, also ist es ein Verweis auf die Funktion sein muss. In ähnlicher Weise über das erste Beispiel würde kompilieren und korrekt auch ohne #undef
ausgeführt werden.
Wenn Sie eine eigene Funktion mit einem Makro-Überschreibung implementieren, können Sie dies für eine gute Wirkung verwenden, obwohl es etwas verwirrend sein könnte, es sei denn, erläutert.
/* 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);
}
Die Funktionsdefinition Zeile aufrufen nicht das Makro, da die Token nach function
nicht (
ist. Die return
Linie wird das Makro aufrufen.
Andere Tipps
Makros werden häufig verwendet, Masse von Code zu generieren. Es ist oft eine ziemlich lokalisierte Nutzung und es ist sicher keine Helfer Makros am Ende des jeweiligen Kopfes um #undef
Namenskonflikte, so dass nur der tatsächlich erzeugten Code importiert wird an anderer Stelle zu vermeiden und die Makros verwendet, um den Code zu generieren, dies nicht tun.
/ Edit: Als Beispiel habe ich verwendet, um diesen structs für mich zu generieren. Der folgende Text ist ein Auszug aus einem aktuellen Projekt:
#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
Da Präprozessor #define
s alle in einem globalen Namensraum sind, ist es einfach für Namespace-Konflikte, führen vor allem, wenn Bibliotheken von Drittanbietern verwenden. Zum Beispiel, wenn Sie eine Funktion namens OpenFile
schaffen wollte, könnte es nicht richtig kompilieren, da die Header-Datei <windows.h>
das Token OpenFile
definiert, um zur Karte entweder OpenFileA
oder OpenFileW
(je nachdem, ob UNICODE
definiert ist oder nicht). Die richtige Lösung ist #undef
OpenFile
vor Ihrer Funktion zu definieren.
Ich kann es nur verwenden, wenn ein Makro in einer #included
-Datei wird mit einem meiner Funktionen zu stören (zum Beispiel es hat den gleichen Namen). Dann #undef
ich das Makro so kann ich meine eigene Funktion.
Obwohl ich glaube, Jonathan Leffler hat Ihnen die richtige Antwort. Hier ist ein sehr seltener Fall, wo ich eine #undef verwenden. Normalerweise sollte ein Makro viele Funktionen wiederverwendbar innen sein; das ist, warum Sie es an der Spitze einer Datei oder in einer Header-Datei definiert werden. Aber manchmal muss man einige sich wiederholende Code innerhalb einer Funktion, die mit einem Makro verkürzt werden kann.
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;
}
, um den Leser zu zeigen, dass dieser Makro in der Funktion nur dann sinnvoll ist, wird es am Ende nicht definiert. Ich möchte nicht, dass jemand ermutigen, solche hackish Makros zu verwenden. Aber wenn Sie zu haben, #undef sie am Ende.
Ist dies eine gängige Praxis, gegen jemanden zu verteidigen # define-ing ein Makro mit dem gleichen Namen wie Ihre Funktion? Oder ist das wirklich mehr von einer Probe, die in der Realität nicht auftreten würde? (EG, niemand in seinen Rechten, falsch noch verrückt Geist Umschreiben getchar werden soll (), so sollte es nicht kommen.)
Ein wenig von beidem. Guter Code wird nicht Gebrauch von #undef
erfordern, aber es gibt viele schlechten Code da draußen Sie haben zu arbeiten. #undef
kann von unschätzbarem Wert sein, wenn jemand einen Trick wie #define bool int
zieht.
Zusätzlich zu den Problemen mit Makros zur Festsetzung des globalen Namensraum verschmutzen, eine weitere Verwendung von #undef
ist die Situation, in der ein Makro erforderlich sein könnte, ein anderes Verhalten an verschiedenen Orten zu haben. Dies ist kein wirklich gemeinsames Szenario, aber ein Paar, das in den Sinn kommen, sind:
-
das
assert
Makro kann hat es Definition in der Mitte einer Übersetzungseinheit für den Fall geändert, wo Sie vielleicht das Debuggen auf einem Teil des Codes auszuführen, andere aber nicht. Zusätzlich zu müssenassert
selbst#undef
'ed wird, dies zu tun, muss derNDEBUG
Makro das gewünschte Verhalten vonassert
neu konfiguriert werden neu definiert werden
-
Ich habe eine Technik, die verwendet gesehen, um sicherzustellen, dass Globals genau einmal definiert ist, ein Makro mithilfe der Variablen als
extern
zu erklären, aber das Makro würde für den einzelnen Fall zu nichts neu definiert werden, wenn die Kopf- / Erklärungen sind verwendet, um die Variablen zu definieren.
So etwas (ich sage nicht, dass dies unbedingt eine gute Technik, nur ein Ich habe in der Wildnis gesehen):
/* 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"
/* ------------------------------------------------------ */
Wenn ein Makro def'ed werden kann, muss eine Anlage undef sein.
ein Speichertracker Ich benutze definiert ihren eigenen neuen / Löschen von Makros Datei / Zeileninformationen zu verfolgen. Dieses Makro bricht den SC ++ L.
#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )
In Bezug auf Ihre spezifische Frage: Namensraum oft emul ist; ated in C durch Bibliothek prefixing Funktionen mit einer Kennung
.Blind Makros undefing wird Verwirrung hinzuzufügen, Wartbarkeit zu reduzieren und kann die Dinge brechen, die auf dem ursprünglichen Verhalten verlassen. Wenn Sie gezwungen wurden, zumindest Verwendung Push / Pop das ursprüngliche Verhalten überall sonst zu bewahren.