Wie in vielen meiner vorherigen Fragen erwähnt, arbeite ich über K & R und bin derzeit im Präprozessor. Eines der interessanteren Dinge - etwas, das ich bei keinem meiner früheren Versuche, C zu lernen, zuvor gekannt habe - ist der ##
Präprozessor-Operator. Laut K & R:
Der Präprozessoroperator
##
bietet eine Möglichkeit, tatsächliche Argumente während der Makroerweiterung zu verketten. Wenn ein Parameter im Ersetzungstext neben a steht##
, wird der Parameter durch das eigentliche Argument ersetzt, der##
und der umgebende Leerraum werden entfernt und das Ergebnis wird erneut gescannt. Das Makropaste
verkettet beispielsweise seine beiden Argumente:
#define paste(front, back) front ## back
So
paste(name, 1)
entsteht das Tokenname1
.
Wie und warum sollte jemand dies in der realen Welt verwenden? Was sind praktische Beispiele für seine Verwendung und gibt es Fallstricke zu berücksichtigen?
std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__);
die gesamte Zeichenfolge reduzieren und implizit auf einmal erstellen.Wenn Sie die Vorverarbeitungsoperatoren Token-Paste ('
##
') oder Stringizing ('#
') verwenden, sollten Sie beachten, dass Sie eine zusätzliche Indirektionsebene verwenden müssen, damit sie in allen Fällen ordnungsgemäß funktionieren.Wenn Sie dies nicht tun und die an den Token-Einfügeoperator übergebenen Elemente selbst Makros sind, erhalten Sie Ergebnisse, die wahrscheinlich nicht Ihren Wünschen entsprechen:
Die Ausgabe:
quelle
__LINE__
handelt sich um einen speziellen Makronamen, der durch den Präprozessor durch die aktuelle Zeilennummer der Quelldatei ersetzt wird.Hier ist ein Fall, auf den ich beim Upgrade auf eine neue Version eines Compilers gestoßen bin:
Die unnötige Verwendung des Token-Pasting-Operators (
##
) ist nicht portierbar und kann zu unerwünschten Leerzeichen, Warnungen oder Fehlern führen.Wenn das Ergebnis des Token-Einfügeoperators kein gültiges Präprozessor-Token ist, ist der Token-Einfügeoperator unnötig und möglicherweise schädlich.
Beispielsweise könnte man versuchen, String-Literale zur Kompilierungszeit mit dem Operator zum Einfügen von Token zu erstellen:
Bei einigen Compilern wird das erwartete Ergebnis ausgegeben:
Bei anderen Compilern umfasst dies unerwünschte Leerzeichen:
Ziemlich moderne Versionen von GCC (> = 3.3 oder so) können diesen Code nicht kompilieren:
Die Lösung besteht darin, den Operator zum Einfügen von Token wegzulassen, wenn Präprozessor-Token mit C / C ++ - Operatoren verkettet werden:
Das Kapitel zur GCC CPP-Dokumentation zur Verkettung enthält weitere nützliche Informationen zum Operator zum Einfügen von Token.
quelle
Dies ist in allen möglichen Situationen nützlich, um sich nicht unnötig zu wiederholen. Das Folgende ist ein Beispiel aus dem Emacs-Quellcode. Wir möchten eine Reihe von Funktionen aus einer Bibliothek laden. Die Funktion "foo" sollte zugewiesen
fn_foo
werden und so weiter. Wir definieren das folgende Makro:Wir können es dann verwenden:
Der Vorteil ist, dass Sie nicht beide schreiben müssen
fn_XpmFreeAttributes
und"XpmFreeAttributes"
(und das Risiko eingehen, einen von ihnen falsch zu schreiben ).quelle
In einer früheren Frage zum Stapelüberlauf wurde nach einer reibungslosen Methode zum Generieren von Zeichenfolgendarstellungen für Aufzählungskonstanten ohne viel fehleranfälliges erneutes Eingeben gefragt.
Verknüpfung
Meine Antwort auf diese Frage zeigte, wie Sie mit wenig Präprozessormagie Ihre Aufzählung wie folgt definieren können (zum Beispiel) ...;
... mit dem Vorteil, dass die Makroerweiterung nicht nur die Aufzählung (in einer .h-Datei) definiert, sondern auch ein passendes Array von Zeichenfolgen (in einer .c-Datei);
Der Name der Zeichenfolgentabelle ergibt sich aus dem Einfügen des Makroparameters (dh Farbe) in StringTable mit dem Operator ##. Bei solchen Anwendungen (Tricks?) Sind die Operatoren # und ## von unschätzbarem Wert.
quelle
Sie können das Einfügen von Token verwenden, wenn Sie Makroparameter mit etwas anderem verketten müssen.
Es kann für Vorlagen verwendet werden:
In diesem Fall würde LINKED_LIST (int) Ihnen geben
Ebenso können Sie eine Funktionsvorlage für die Listenüberquerung schreiben.
quelle
Ich verwende es in C-Programmen, um die Prototypen für eine Reihe von Methoden korrekt durchzusetzen, die einer Art Aufrufkonvention entsprechen müssen. In gewisser Weise kann dies für die Objektorientierung des armen Mannes in geradem C verwendet werden:
erweitert sich zu so etwas:
Dies erzwingt die korrekte Parametrisierung für alle "abgeleiteten" Objekte, wenn Sie Folgendes tun:
Das Obige in Ihren Header-Dateien usw. Es ist auch nützlich für die Wartung, wenn Sie die Definitionen ändern und / oder den "Objekten" Methoden hinzufügen möchten.
quelle
SGlib verwendet ##, um Vorlagen in C grundsätzlich zu fudgen . Da keine Funktionsüberladung vorliegt, wird ## verwendet, um den Typnamen in die Namen der generierten Funktionen zu kleben. Wenn ich einen Listentyp namens list_t hätte, würde ich Funktionen wie sglib_list_t_concat usw. erhalten.
quelle
Ich verwende es für einen Home Rolled Assert auf einem nicht standardmäßigen C-Compiler für Embedded:
quelle
##
?Ich verwende es zum Hinzufügen von benutzerdefinierten Präfixen zu Variablen, die durch Makros definiert sind. Also so etwas wie:
erweitert sich zu:
quelle
Die Hauptanwendung ist, wenn Sie eine Namenskonvention haben und Ihr Makro diese Namenskonvention nutzen soll. Möglicherweise haben Sie mehrere Methodenfamilien: image_create (), image_activate () und image_release () sowie file_create (), file_activate (), file_release () und mobile_create (), mobile_activate () und mobile_release ().
Sie könnten ein Makro für die Behandlung des Objektlebenszyklus schreiben:
Natürlich ist eine Art "minimale Version von Objekten" nicht die einzige Art von Namenskonvention, für die dies gilt - fast die überwiegende Mehrheit der Namenskonventionen verwendet eine gemeinsame Teilzeichenfolge, um die Namen zu bilden. Es könnte mir Funktionsnamen (wie oben) oder Feldnamen, Variablennamen oder fast alles andere geben.
quelle
Eine wichtige Verwendung in WinCE:
Bei der Definition der Registerbitbeschreibung gehen wir wie folgt vor:
Und während Sie BITFMASK verwenden, verwenden Sie einfach:
quelle
Es ist sehr nützlich für die Protokollierung. Du kannst tun:
Oder, wenn Ihr Compiler unterstützt keine Funktion und func :
Die obigen "Funktionen" protokollieren die Nachricht und zeigen genau, welche Funktion eine Nachricht protokolliert hat.
Meine C ++ - Syntax ist möglicherweise nicht ganz korrekt.
quelle