Direktiven sind spezielle Anweisungen, die an den Präprozessor (Präprozessor-Direktive) oder an den Compiler (Compiler-Direktive) gerichtet sind, wie er einen Teil oder den gesamten Quellcode verarbeiten oder einige Flags setzen soll Das endgültige Objekt und werden verwendet, um das Schreiben von Quellcode zu vereinfachen (z. B. portabler) und den Quellcode verständlicher zu machen. Direktiven werden vom Präprozessor verarbeitet, der entweder ein separates Programm ist, das vom Compiler aufgerufen wird, oder ein Teil des Compilers selbst.
#includeEdit
C hat einige Funktionen als Teil der Sprache und einige andere als Teil einer Standardbibliothek, die ein Repository von Code ist, der neben jedem standardkonformen C-Compiler verfügbar ist. Wenn der C-Compiler Ihr Programm kompiliert, verknüpft er es normalerweise auch mit der Standard-C-Bibliothek. Wenn beispielsweise eine #include <stdio.h>
Direktive #include <stdio.h>
, wird die Direktive durch den Inhalt des stdio ersetzt.h Header-Datei.
Wenn Sie Features aus der Bibliothek verwenden, müssen Sie in C angeben, was Sie verwenden würden. Die erste Zeile im Programm ist eine Vorverarbeitungsdirektive, die folgendermaßen aussehen sollte:
#include <stdio.h>
Die obige Zeile verursacht die C-Deklarationen, die sich im stdio befinden.h-Header für die Verwendung in Ihrem Programm enthalten sein. Normalerweise wird dies implementiert, indem Sie einfach den Inhalt einer Header-Datei namens stdio in Ihr Programm einfügen.h, befindet sich an einem systemabhängigen Ort. Der Speicherort solcher Dateien kann in der Dokumentation Ihres Compilers beschrieben werden. Eine Liste der Standard-C-Header-Dateien ist unten in der Header-Tabelle aufgeführt.
Das stdio.der h-Header enthält verschiedene Deklarationen für Eingabe / Ausgabe (E / A) unter Verwendung einer Abstraktion von E / A-Mechanismen, die als Streams bezeichnet werden. Beispielsweise gibt es ein Ausgabestreamobjekt namens stdout, mit dem Text in die Standardausgabe ausgegeben wird, die normalerweise den Text auf dem Computerbildschirm anzeigt.
Wenn Sie wie im obigen Beispiel spitze Klammern verwenden, wird der Präprozessor angewiesen, entlang des Pfads der Entwicklungsumgebung nach der Include-Datei für die Standard-Includes zu suchen.
#include "other.h"
Wenn Sie Anführungszeichen (“ „) verwenden, wird erwartet, dass der Präprozessor an einigen zusätzlichen, normalerweise benutzerdefinierten Speicherorten nach der Header-Datei sucht und nur dann auf die Standard-Include-Pfade zurückgreift, wenn er an diesen zusätzlichen Speicherorten nicht gefunden wird. Es ist üblich, dass dieses Formular die Suche im selben Verzeichnis wie die Datei einschließt, die die Direktive #include enthält.
HINWEIS: Sie sollten die Dokumentation der von Ihnen verwendeten Entwicklungsumgebung auf herstellerspezifische Implementierungen der Direktive #include überprüfen.
HeadersEdit
The C90 standard headers list:
|
|
|
Headers added since C90:
|
|
|
#pragmaEdit
Die Pragma-Direktive (pragmatic information) ist Teil des Standards, aber die Bedeutung jedes Pragmas hängt von der Softwareimplementierung des Standards ab, der verwendet wird verwendet. Die #pragma Direktive bietet eine Möglichkeit, spezielles Verhalten vom Compiler anzufordern. Diese Direktive ist am nützlichsten für Programme, die ungewöhnlich groß sind oder die die Funktionen eines bestimmten Compilers nutzen müssen.
Pragmas werden innerhalb des Quellprogramms verwendet.
#pragma token(s)
- Auf Pragma folgt normalerweise ein einzelnes Token, das einen Befehl darstellt, dem der Compiler gehorchen muss. Sie sollten die Softwareimplementierung des C-Standards, den Sie verwenden möchten, auf eine Liste der unterstützten Token überprüfen. Es überrascht nicht, dass der Satz von Befehlen, die in #Pragma-Direktiven erscheinen können, für jeden Compiler unterschiedlich ist; Sie müssen die Dokumentation für Ihren Compiler konsultieren, um zu sehen, welche Befehle es erlaubt und was diese Befehle tun.
Zum Beispiel zeigt eine der am häufigsten implementierten Präprozessor-Direktiven, #pragma once
, wenn sie am Anfang einer Header-Datei platziert wird, an, dass die Datei, in der sie sich befindet, übersprungen wird, wenn sie mehrmals vom Präprozessor aufgenommen wird.
HINWEIS: Es gibt andere Methoden, um diese Aktion auszuführen, die allgemein als Verwendung von Include-Wachen bezeichnet wird.
#defineEdit
WARNUNG: Präprozessor-Makros sind zwar verlockend, können jedoch zu unerwarteten Ergebnissen führen, wenn sie nicht richtig ausgeführt werden. Denken Sie immer daran, dass Makros Textersetzungen sind, die an Ihrem Quellcode vorgenommen werden, bevor etwas kompiliert wird. Der Compiler weiß nichts über die Makros und bekommt sie nie zu sehen. Dies kann unter anderem zu obskuren Fehlern führen. Verwenden Sie lieber Sprachfunktionen, wenn diese gleichwertig sind (Verwenden Sie im Beispiel const int
oder enum
anstelle von #define
d Konstanten).
Es gibt jedoch Fälle, in denen Makros sehr nützlich sind (siehe das debug
Makro unten für ein Beispiel).
Die Direktive #define wird verwendet, um Werte oder Makros zu definieren, die vom Präprozessor verwendet werden, um den Programmquellcode zu manipulieren, bevor er kompiliert wird. Da Präprozessordefinitionen ersetzt werden, bevor der Compiler auf den Quellcode einwirkt, sind alle durch #define eingeführten Fehler schwer zu verfolgen.
Per Konvention werden mit #define definierte Werte in Großbuchstaben benannt. Obwohl dies keine Voraussetzung ist, wird es als sehr schlechte Praxis angesehen, etwas anderes zu tun. Dadurch können die Werte beim Lesen des Quellcodes leicht identifiziert werden.
Heute wird #define hauptsächlich verwendet, um Compiler- und Plattformunterschiede zu behandeln. Beispielsweise kann ein define eine Konstante enthalten, die der geeignete Fehlercode für einen Systemaufruf ist. Die Verwendung von #define sollte daher eingeschränkt werden, es sei denn, dies ist absolut notwendig; typedef-Anweisungen und konstante Variablen können häufig dieselben Funktionen sicherer ausführen.
Ein weiteres Merkmal des #define-Befehls ist, dass er Argumente annehmen kann, was ihn als Pseudofunktionsersteller ziemlich nützlich macht. Betrachten Sie den folgenden Code:
#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}
Es ist generell eine gute Idee, bei komplexen Makros zusätzliche Klammern zu verwenden. Beachten Sie, dass im obigen Beispiel die Variable „x“ immer in einem eigenen Satz von Klammern steht. Auf diese Weise wird es vollständig ausgewertet, bevor es mit 0 verglichen oder mit -1 multipliziert wird. Außerdem ist das gesamte Makro von Klammern umgeben, um zu verhindern, dass es durch anderen Code kontaminiert wird. Wenn Sie nicht vorsichtig sind, laufen Sie Gefahr, dass der Compiler Ihren Code falsch interpretiert.
Aufgrund von Nebenwirkungen wird es als sehr schlechte Idee angesehen, Makrofunktionen wie oben beschrieben zu verwenden.
int x = -10;int y = ABSOLUTE_VALUE( x++ );
Wenn ABSOLUTE_VALUE() eine echte Funktion wäre, hätte ‚x‘ jetzt den Wert ‚-9‘, aber weil es ein Argument in einem Makro war, wurde es zweimal erweitert und hat somit einen Wert von -8.
Beispiel:
Um die Gefahren von Makros zu veranschaulichen, betrachten Sie dieses naive Makro
#define MAX(a,b) a>b?a:b
und den Code
i = MAX(2,3)+5;j = MAX(3,2)+5;
Schauen Sie sich das an und überlegen Sie, was der Wert nach der Ausführung sein könnte. Die Anweisungen werden in
int i = 2>3?2:3+5;int j = 3>2?3:2+5;
Also nach der Ausführung i=8 und j=3 anstelle des erwarteten Ergebnisses von i=j=8! Aus diesem Grund wurden Sie gewarnt, einen zusätzlichen Satz von Klammern oben zu verwenden, aber auch mit diesen, Die Straße ist voller Gefahren. Der Alert-Leser könnte schnell erkennen, dass, wenn a
oder b
Ausdrücke enthält, die Definition jede Verwendung von a,b in der Makrodefinition wie folgt in Klammern setzen muss:
#define MAX(a,b) ((a)>(b)?(a):(b))
Dies funktioniert, vorausgesetzt, a, b haben keine Nebenwirkungen. Tatsächlich würde
i = 2;j = 3;k = MAX(i++, j++);
zu k=4, i=3 und j=5 führen. Dies wäre sehr überraschend für jeden, der erwartet, dass sich MAX() wie eine Funktion verhält.
Also, was ist die richtige Lösung? Die Lösung besteht darin, überhaupt kein Makro zu verwenden. Eine globale Inline-Funktion wie diese
inline int max(int a, int b) { return a>b?a:b }
hat keine der oben genannten Fallstricke, funktioniert aber nicht mit allen Typen.
HINWEIS: Die explizite inline
Deklaration ist nicht wirklich notwendig, es sei denn, die Definition befindet sich in einer Header-Datei, da Ihr Compiler Funktionen für Sie einbinden kann (mit gcc kann dies mit -finline-functions
oder -O3
). Der Compiler ist oft besser als der Programmierer bei der Vorhersage, welche Funktionen es wert sind, eingefügt zu werden. Außerdem sind Funktionsaufrufe nicht wirklich teuer (früher).
Der Compiler kann das Schlüsselwort inline
ignorieren. Es ist nur ein Hinweis (außer dass inline
erforderlich ist, damit eine Funktion in einer Header-Datei definiert werden kann, ohne dass eine Fehlermeldung generiert wird, da die Funktion in mehr als einer Übersetzungseinheit definiert ist).
(#, ##)
Die Operatoren # und ## werden mit dem Makro #define verwendet. Die Verwendung von # bewirkt, dass das erste Argument nach dem # als String in Anführungszeichen zurückgegeben wird. Zum Beispiel wird der Befehl
#define as_string( s ) # s
dazu führen, dass der Compiler diesen Befehl
puts( as_string( Hello World! ) ) ;
in
puts( "Hello World!" );
Mit ## verkettet, was vor dem ## mit dem, was danach ist. Zum Beispiel wird der Befehl
#define concatenate( x, y ) x ## y...int xy = 10;...
den Compiler dazu bringen,
printf( "%d", concatenate( x, y ));
in
printf( "%d", xy);
umzuwandeln, was natürlich 10 zur Standardausgabe anzeigt.
Es ist möglich, ein Makroargument mit einem konstanten Präfix oder Suffix zu verketten, um eine gültige Kennung zu erhalten, wie in
#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )
, die eine Funktion namens my_bar() definiert. Es ist jedoch nicht möglich, ein Makroargument mithilfe des Verkettungsoperators in eine konstante Zeichenfolge zu integrieren. Um einen solchen Effekt zu erzielen, kann man die ANSI C-Eigenschaft verwenden, dass zwei oder mehr aufeinanderfolgende Zeichenfolgenkonstanten als äquivalent zu einer einzelnen Zeichenfolgenkonstante betrachtet werden, wenn sie angetroffen werden. Mit dieser Eigenschaft kann man schreiben
#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )
was der Makroprozessor in
puts( "I'm eating " "fruit" " today." )
was wiederum vom C-Parser als einzelne String-Konstante interpretiert wird.
Der folgende Trick kann verwendet werden, um numerische Konstanten in String-Literale umzuwandeln
#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));
Dies ist etwas schwierig, da es in 2 Schritten erweitert wird. Zuerst wird num2str(CONST)
durch str(23)
ersetzt, was wiederum durch "23"
ersetzt wird. Dies kann im folgenden Beispiel nützlich sein:
#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif
Dies gibt Ihnen eine nette Debug-Nachricht mit der Datei und der Zeile, in der die Nachricht ausgegeben wurde. Wenn DEBUG jedoch nicht definiert ist, verschwindet die Debugmeldung vollständig aus Ihrem Code. Achten Sie darauf, diese Art von Konstrukt nicht mit Nebenwirkungen zu verwenden, da dies zu Fehlern führen kann, die abhängig von den Kompilierungsparametern auftreten und verschwinden.
macrosEdit
Makros werden nicht typgeprüft und werten daher keine Argumente aus. Außerdem befolgen sie den Gültigkeitsbereich nicht ordnungsgemäß, sondern nehmen einfach die an sie übergebene Zeichenfolge und ersetzen jedes Vorkommen des Makroarguments im Text des Makros durch die tatsächliche Zeichenfolge für diesen Parameter (der Code wird buchstäblich an die Stelle kopiert, von der aus er aufgerufen wurde).
Ein Beispiel für die Verwendung eines Makros:
#include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }
— das Ergebnis von „a“ sollte „2“ sein (b + c = 16 -> übergeben an ADD -> 16 / C -> Ergebnis ist „2“)
HINWEIS:
Es ist normalerweise eine schlechte Praxis, Makros in Kopfzeilen zu definieren.
Ein Makro sollte nur definiert werden, wenn es nicht möglich ist, dasselbe Ergebnis mit einer Funktion oder einem anderen Mechanismus zu erzielen. Einige Compiler sind in der Lage, Code so zu optimieren, dass Aufrufe kleiner Funktionen durch Inline-Code ersetzt werden, wodurch mögliche Geschwindigkeitsvorteile zunichte gemacht werden.Die Verwendung von Typedefs, Enums und Inline (in C99) ist oft eine bessere Option.
Eine der wenigen Situationen, in denen Inline-Funktionen nicht funktionieren – Sie sind also gezwungen, stattdessen funktionsähnliche Makros zu verwenden -, besteht darin, Kompilierungszeitkonstanten zu initialisieren (statische Initialisierung von Strukturen).Dies geschieht, wenn die Argumente für das Makro Literale sind, die der Compiler auf ein anderes Literal optimieren kann.
#errorEdit
Die Direktive #error stoppt die Kompilierung. Wenn einer gefunden wird, gibt der Standard an, dass der Compiler eine Diagnose ausgeben soll, die die verbleibenden Token in der Direktive enthält. Dies wird hauptsächlich für Debugging-Zwecke verwendet.Programmierer verwenden „#error“ innerhalb eines bedingten Blocks, um den Compiler sofort anzuhalten, wenn das „#if“ oder „#ifdef“ – am Anfang des Blocks – ein Kompilierungsproblem erkennt.Normalerweise überspringt der Compiler den Block (und die darin enthaltene Direktive „#error“) und die Kompilierung wird fortgesetzt.
#error message
#warningEdit
Viele Compiler unterstützen eine #warning Direktive. Wenn einer gefunden wird, gibt der Compiler eine Diagnose aus, die die verbleibenden Token in der Direktive enthält.
#warning message
#undef
Die Direktive #undef macht ein Makro rückgängig. Der Bezeichner muss nicht vorher definiert worden sein.
#if,#else,#elif,#endif (conditionals)Edit
Der Befehl #if prüft, ob ein steuernder Bedingungsausdruck Null oder ungleich Null ergibt, und schließt einen Codeblock aus bzw. schließt ihn ein. Beispiel:
#if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif
Der Bedingungsausdruck könnte einen beliebigen C-Operator enthalten, mit Ausnahme der Zuweisungsoperatoren, der Inkrement- und Dekrementoperatoren, des Adress-of-Operators und des sizeof-Operators.
Ein eindeutiger Operator, der in der Vorverarbeitung verwendet wird, und nirgendwo sonst ist der definierte Operator. Es gibt 1 zurück, wenn der Makroname, optional in Klammern eingeschlossen, derzeit definiert ist; 0 wenn nicht.
Der Befehl #endif beendet einen Block, der mit #if
#ifdef
oder #ifndef
gestartet wurde.
Der Befehl #elif ähnelt #if
, außer dass er verwendet wird, um einen aus einer Reihe von Codeblöcken zu extrahieren. ZB:
#if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */
#ifdef,#ifndefEdit
Der Befehl #ifdef ähnelt #if
, außer dass der darauf folgende Codeblock ausgewählt wird, wenn ein Makroname definiert ist. In dieser Hinsicht
#ifdef NAME
entspricht
#if defined NAME
Der Befehl #ifndef ähnelt #ifdef, außer dass der Test umgekehrt wird:
#ifndef NAME
entspricht
#if !defined NAME
#LineEdit
Diese Präprozessor-Direktive wird verwendet, um den Dateinamen und die Zeilennummer der Zeile nach der Direktive auf neue Werte zu setzen. Dies wird verwendet, um die Makros __FILE__ und __LINE__ festzulegen.