Suche…


Einführung

Alle Präprozessorbefehle beginnen mit dem Hash-Symbol # . AC-Makro ist nur ein Präprozessorbefehl, der mit der #define Präprozessor-Direktive definiert wird. Während der Vorverarbeitungsphase ersetzt der C-Präprozessor (ein Teil des C-Compilers) einfach den Hauptteil des Makros, wo immer sein Name erscheint.

Bemerkungen

Wenn ein Compiler auf ein Makro im Code stößt, führt er einen einfachen String-Austausch durch. Es werden keine zusätzlichen Operationen ausgeführt. Aus diesem Grund berücksichtigen Änderungen durch den Präprozessor nicht den Geltungsbereich von C-Programmen. Beispielsweise ist eine Makrodefinition nicht darauf beschränkt, innerhalb eines Blocks zu liegen, und wird daher nicht durch ein '}' , das eine Blockanweisung beendet.

Der Präprozessor ist vom Entwurf her nicht vollständig - es gibt verschiedene Arten von Berechnungen, die nicht vom Präprozessor allein ausgeführt werden können.

Normalerweise verfügen Compiler über ein Befehlszeilenflag (oder eine Konfigurationseinstellung), mit dem wir die Kompilierung nach der Vorverarbeitungsphase stoppen und das Ergebnis überprüfen können. Auf POSIX-Plattformen ist dieses Flag -E . Wenn Sie gcc mit diesem Flag ausführen, wird die erweiterte Quelle in stdout ausgegeben:

$ gcc -E cprog.c

Oft ist der Präprozessor als separates Programm implementiert, das vom Compiler aufgerufen wird. Der gebräuchliche Name für dieses Programm lautet cpp . Eine Reihe von Präprozessoren gibt unterstützende Informationen aus, z. B. Informationen zu Zeilennummern, die von den nachfolgenden Kompilierungsphasen verwendet werden, um Debugging-Informationen zu generieren. Wenn der Präprozessor auf gcc basiert, unterdrückt die Option -P diese Informationen.

$ cpp -P cprog.c

Bedingter Einschluss und Änderung der bedingten Funktionssignatur

Bedingt einen Codeblock enthalten, weist der Vorprozessor mehrere Richtlinien (zB #if , #ifdef , #else , #endif , etc).

/* Defines a conditional `printf` macro, which only prints if `DEBUG`
 * has been defined
 */
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Für die #if Bedingung können normale C-Vergleichsoperatoren verwendet werden

#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif

Die #if Direktive verhält sich ähnlich wie die C if #if Sie enthält nur integrale konstante Ausdrücke und keine Casts. Es unterstützt einen zusätzlichen unären Operator ( defined( identifier ) , der 1 zurückgibt, wenn der Bezeichner definiert ist, andernfalls 0 .

#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Bedingte Funktionssignaturänderung

In den meisten Fällen wird erwartet, dass ein Release-Build einer Anwendung so wenig Overhead wie möglich hat. Beim Testen eines vorläufigen Builds können jedoch zusätzliche Protokolle und Informationen zu gefundenen Problemen hilfreich sein.

SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) es gibt eine Funktion SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) die bei einem Test-Build gewünscht wird, erzeugt ein Protokoll über ihre Verwendung. Diese Funktion wird jedoch an mehreren Stellen verwendet, und es ist erwünscht, dass bei der Erzeugung des Protokolls ein Teil der Informationen darin besteht, zu wissen, von wo die Funktion aufgerufen wird.

Wenn Sie die bedingte Kompilierung verwenden, können Sie in der Include-Datei so etwas wie das Folgende definieren, das die Funktion deklariert. Dies ersetzt die Standardversion der Funktion durch eine Debug-Version der Funktion. Der Präprozessor wird verwendet, um Aufrufe der Funktion SerOpPluAllRead() mit Aufrufen der Funktion SerOpPluAllRead_Debug() durch zwei zusätzliche Argumente zu ersetzen, den Dateinamen und die Zeilennummer der Funktion, in der die Funktion verwendet wird.

Bei der bedingten Kompilierung wird ausgewählt, ob die Standardfunktion mit einer Debugversion überschrieben werden soll oder nicht.

#if 0
// function declaration and prototype for our debug version of the function.
SHORT   SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);

// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif

Auf diese Weise können Sie die Standardversion der Funktion SerOpPluAllRead() mit einer Version überschreiben, die den Namen der Datei und die Zeilennummer in der Datei SerOpPluAllRead() in der die Funktion aufgerufen wird.

Es gibt eine wichtige Überlegung: Jede Datei, die diese Funktion verwendet, muss die Header-Datei enthalten, bei der dieser Ansatz verwendet wird, damit der Präprozessor die Funktion ändern kann. Andernfalls wird ein Linker-Fehler angezeigt.

Die Definition der Funktion würde ungefähr wie folgt aussehen. Diese Quelle fordert an, dass der Präprozessor die Funktion SerOpPluAllRead() in SerOpPluAllRead_Debug() umbenennt und die Argumentliste so ändert, dass sie zwei zusätzliche Argumente enthält, einen Zeiger auf den Namen der Datei, in der die Funktion aufgerufen wurde, und die Zeilennummer in der Datei, in der die Funktion verwendet wird.

#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);

SHORT    SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
    int iLen = 0;
    char  xBuffer[256];

    // only print the last 30 characters of the file name to shorten the logs.
    iLen = strlen (aszFilePath);
    if (iLen > 30) {
        iLen = iLen - 30;
    }
    else {
        iLen = 0;
    }

    sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
    IssueDebugLog(xBuffer);

    // now that we have issued the log, continue with standard processing.
    return SerOpPluAllRead_Special(pPif, usLockHnd);
}

// our special replacement function name for when we are generating logs.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
    if (STUB_SELF == SstReadAsMaster()) {
        return OpPluAllRead(pPif, usLockHnd);
    } 
    return OP_NOT_MASTER;
}

Quelldatei-Aufnahme

Die häufigsten Verwendungen von #include Vorverarbeitungsrichtlinien sind wie folgt:

#include <stdio.h>
#include "myheader.h"

#include ersetzt die Anweisung durch den Inhalt der Datei, auf die verwiesen wird. Eckige Klammern (<>) beziehen sich auf im System installierte Header-Dateien, während Anführungszeichen ("") für vom Benutzer angegebene Dateien stehen.

Makros selbst können andere Makros einmal erweitern, wie dieses Beispiel veranschaulicht:

#if VERSION == 1
    #define INCFILE  "vers1.h"
#elif VERSION == 2
    #define INCFILE  "vers2.h"
    /*  and so on */
#else
    #define INCFILE  "versN.h"
#endif
/* ... */
#include INCFILE

Makro-Ersatz

Die einfachste Form der Makroersetzung besteht darin, eine manifest constant wie in zu definieren

#define ARRSIZE 100
int array[ARRSIZE];

Dies definiert ein funktionsähnliches Makro, das eine Variable mit 10 multipliziert und den neuen Wert speichert:

#define TIMES10(A) ((A) *= 10)

double b = 34;
int c = 23;

TIMES10(b);   // good: ((b) *= 10);
TIMES10(c);   // good: ((c) *= 10);
TIMES10(5);   // bad:  ((5) *= 10);

Die Ersetzung erfolgt vor jeder anderen Interpretation des Programmtextes. Beim ersten Aufruf von TIMES10 der Name A aus der Definition durch b und der so erweiterte Text wird an die Stelle des Aufrufs gesetzt. Beachten Sie, dass diese Definition von TIMES10 nicht äquivalent zu ist

#define TIMES10(A) ((A) = (A) * 10)

weil dies den Ersatz von A zweimal auswerten könnte, was unerwünschte Nebenwirkungen haben kann.

Im Folgenden wird ein funktionsähnliches Makro definiert, dessen Wert das Maximum seiner Argumente ist. Es hat die Vorteile, für alle kompatiblen Typen der Argumente zu arbeiten und Inline-Code zu generieren, ohne den Funktionsaufruf zu verursachen. Es hat den Nachteil, das eine oder andere seiner Argumente ein zweites Mal zu bewerten (einschließlich Nebenwirkungen) und bei mehrmaligem Aufruf mehr Code als eine Funktion zu generieren.

#define max(a, b) ((a) > (b) ? (a) : (b))

int maxVal = max(11, 43);              /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */

/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j);       /* i == 4 */

Aus diesem Grund werden Makros, die ihre Argumente mehrfach auswerten, im Produktionscode normalerweise vermieden. Seit C11 gibt es die _Generic Funktion, mit der solche mehrfachen _Generic können.

Die reichlichen Klammern in den Makroerweiterungen (rechte Seite der Definition) stellen sicher, dass die Argumente und der resultierende Ausdruck ordnungsgemäß gebunden sind und gut in den Kontext passen, in dem das Makro aufgerufen wird.

Fehleranweisung

Wenn der Präprozessor auf eine #error Anweisung #error , wird die Kompilierung angehalten und die enthaltene Diagnosemeldung wird gedruckt.

#define DEBUG

#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif

int main(void) {
    return 0;
}

Mögliche Ausgabe:

$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"

#if 0, um Codeabschnitte auszublenden

Wenn Sie Codeabschnitte in Betracht ziehen, die Sie entfernen möchten oder vorübergehend deaktivieren möchten, können Sie sie mit einem Blockkommentar auskommentieren.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
*/

Wenn der Quellcode, den Sie mit einem Blockkommentar umgeben haben, im Quellcode Blockkommentare enthält, kann die Endung * / der vorhandenen Blockkommentare dazu führen, dass Ihr neuer Blockkommentar ungültig wird und zu Kompilierungsproblemen führt.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;

    /* Return 5 */
    return i;
}
*/ 

Im vorherigen Beispiel werden die letzten beiden Zeilen der Funktion und die letzten '* /' vom Compiler gesehen, sodass sie mit Fehlern kompiliert werden könnten. Eine sicherere Methode ist die Verwendung einer #if 0 Direktive um den Code, den Sie blockieren möchten.

#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
 * removed by the preprocessor. */
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
#endif

Dies hat den Vorteil, dass es einfacher ist, nach "#if 0" zu suchen, als nach allen Kommentaren zu suchen, wenn Sie den Code wiederfinden möchten.

Ein weiterer sehr wichtiger Vorteil ist, dass Sie den Code mit #if 0 . Dies ist nicht mit Kommentaren möglich.

Eine Alternative zur Verwendung von #if 0 besteht darin, einen Namen zu verwenden, der nicht #defined aber mehr beschreibt, warum der Code blockiert wird. Wenn es beispielsweise eine Funktion gibt, die scheinbar unbrauchbar ist, können Sie #if defined(POSSIBLE_DEAD_CODE) oder #if defined(FUTURE_CODE_REL_020201) für Code verwenden, der benötigt wird, wenn eine andere Funktionalität vorhanden ist oder ähnliches. Wenn Sie dann diese Quelle erneut entfernen oder aktivieren, sind diese Quellbereiche leicht zu finden.

Token-Einfügen

Beim Token-Einfügen können zwei Makro-Argumente miteinander verbunden werden. Zum Beispiel ergibt front##back frontback front##back frontback . Ein bekanntes Beispiel ist der <TCHAR.H> -Header von Win32. In Standard C kann man L"string" schreiben, um eine breite Zeichenkette zu deklarieren. Allerdings ermöglicht Windows - API einen weiten Zeichenketten und schmalen Zeichenketten einfach durch konvertieren #define ing UNICODE . Um die String-Literale zu implementieren, verwendet TCHAR.H dies

#ifdef UNICODE
#define TEXT(x) L##x
#endif

Wenn ein Benutzer TEXT("hello, world") schreibt TEXT("hello, world") und UNICODE definiert ist, verkettet der C-Präprozessor L und das Makro-Argument. L mit "hello, world" verkettet gibt L"hello, world" .

Vordefinierte Makros

Ein vordefiniertes Makro ist ein Makro, das vom C-Pre-Prozessor bereits verstanden wird, ohne dass das Programm ihn definieren muss. Beispiele beinhalten

Obligatorische vordefinierte Makros

  • __FILE__ , die den Dateinamen der aktuellen Quelldatei (ein String-Literal) __FILE__ ,
  • __LINE__ für die aktuelle Zeilennummer (eine Ganzzahlkonstante),
  • __DATE__ für das Kompilierungsdatum (ein String-Literal),
  • __TIME__ für die Kompilierungszeit (ein String-Literal).

Es gibt auch einen verwandten vordefinierten Bezeichner __func__ (ISO / IEC 9899: 2011 §6.4.2.2), der kein Makro ist:

Der Bezeichner __func__ ist vom Übersetzer implizit zu deklarieren, als würde die Deklaration unmittelbar auf die öffnenden Klammern jeder Funktionsdefinition folgen:

 static const char __func__[] = "function-name";

erschienen, wobei Funktionsname der Name der lexikalisch einschließenden Funktion ist.

__FILE__ , __LINE__ und __func__ sind besonders für Debugging-Zwecke nützlich. Zum Beispiel:

fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);

Pre-C99-Compiler unterstützen möglicherweise __func__ oder verfügen möglicherweise nicht über ein Makro, das dasselbe __func__ und anders benannt wird. Beispielsweise verwendete gcc __FUNCTION__ im C89-Modus.

Mit den folgenden Makros können Sie Details zur Implementierung abfragen:

  • __STDC_VERSION__ Die Version des C-Standards wurde implementiert. Dies ist eine konstante Ganzzahl, die das Format yyyymmL (der Wert 201112L für C11, der Wert 199901L für C99; es wurde nicht für C89 / C90 definiert).
  • __STDC_HOSTED__ 1 wenn es sich um eine gehostete Implementierung handelt, andernfalls 0 .
  • __STDC__ Wenn 1 , entspricht die Implementierung dem C-Standard.

Andere vordefinierte Makros (nicht obligatorisch)

ISO / IEC 9899: 2011 §6.10.9.2 Umgebungsmakros:

  • __STDC_ISO_10646__ Eine Ganzzahlkonstante der Form yyyymmL (zum Beispiel 199712L). Wenn dieses Symbol definiert ist, hat jedes Zeichen in der erforderlichen Unicode-Gruppe, wenn es in einem Objekt vom Typ wchar_t gespeichert ist, denselben Wert wie der Kurzbezeichner dieses Zeichens. Das erforderliche Unicode-Set besteht aus allen Zeichen, die in der ISO / IEC 10646 definiert sind, einschließlich aller Änderungen und technischen Korrelationen ab dem angegebenen Jahr und Monat. Wenn eine andere Codierung verwendet wird, darf das Makro nicht definiert werden und die tatsächliche Codierung ist implementierungsdefiniert.

  • __STDC_MB_MIGHT_NEQ_WC__ Die Integer-Konstante 1, die angeben soll, dass ein Member des Basis-Zeichensatzes in der Codierung für wchar_t keinen Codewert haben muss, der seinem Wert entspricht, wenn er als Einzelzeichen in einer Integer-Zeichenkonstante verwendet wird.

  • __STDC_UTF_16__ Die Ganzzahlkonstante 1, die anzeigen soll, dass Werte vom Typ char16_t UTF-16-codiert sind. Wenn eine andere Codierung verwendet wird, darf das Makro nicht definiert werden und die tatsächliche Codierung ist implementierungsdefiniert.

  • __STDC_UTF_32__ Die Ganzzahlkonstante 1, die anzeigen soll, dass Werte vom Typ char32_t UTF-32-codiert sind. Wenn eine andere Codierung verwendet wird, darf das Makro nicht definiert werden und die tatsächliche Codierung ist implementierungsdefiniert.

ISO / IEC 9899: 2011 §6.10.8.3 Makros für bedingte Merkmale

  • __STDC_ANALYZABLE__ Die Ganzzahlkonstante 1, die die Übereinstimmung mit den Spezifikationen in Anhang L (Analysierbarkeit) anzeigen soll.
  • __STDC_IEC_559__ Die Ganzzahlkonstante 1, die die Übereinstimmung mit den Spezifikationen in Anhang F (IEC 60559-Gleitkomma-Arithmetik) anzeigen soll.
  • __STDC_IEC_559_COMPLEX__ Die Ganzzahlkonstante 1, die die Einhaltung der Spezifikationen in Anhang G (IEC 60559-kompatible komplexe Arithmetik) anzeigen soll.
  • __STDC_LIB_EXT1__ Die Ganzzahlkonstante 201112L , die die Unterstützung für die in Anhang K definierten Erweiterungen (Bounds-testing Interfaces) 201112L soll.
  • __STDC_NO_ATOMICS__ Die Ganzzahlkonstante 1, die darauf hinweist, dass die Implementierung atomare Typen (einschließlich des _Atomic Typ-Qualifiers) und den Header <stdatomic.h> nicht unterstützt.
  • __STDC_NO_COMPLEX__ Die Ganzzahlkonstante 1, die darauf hinweist, dass die Implementierung keine komplexen Typen oder den Header <complex.h> .
  • __STDC_NO_THREADS__ Die Ganzzahlkonstante 1, die darauf hinweisen soll, dass die Implementierung den Header <threads.h> nicht unterstützt.
  • __STDC_NO_VLA__ Die Ganzzahlkonstante 1, die darauf hinweist, dass die Implementierung Arrays mit variabler Länge oder variabel modifizierte Typen nicht unterstützt.

Header Include Guards

So ziemlich jede Header-Datei sollte dem Include Guard- Idiom folgen:

meine-Header-Datei.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Dadurch wird sichergestellt, dass Sie, wenn Sie #include "my-header-file.h" an mehreren Stellen #include "my-header-file.h" , keine doppelten Deklarationen von Funktionen, Variablen usw. erhalten. Stellen Sie sich die folgende Hierarchie der Dateien vor:

header-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

header-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

Haupt c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Dieser Code hat ein schwerwiegendes Problem: Der detaillierte Inhalt von MyStruct ist zweimal definiert, was nicht zulässig ist. Dies würde zu einem Kompilierungsfehler führen, der schwer zu finden ist, da eine Headerdatei eine andere enthält. Wenn Sie es stattdessen mit Header Guards getan haben:

header-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

header-2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

Haupt c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Dies würde sich dann erweitern auf:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

Wenn der Compiler die zweite Aufnahme von Header-1.h erreicht , wurde HEADER_1_H bereits durch die vorherige Aufnahme definiert. Ergo läuft es auf folgendes hinaus:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

Und somit gibt es keinen Kompilierungsfehler.

Hinweis: Es gibt mehrere verschiedene Konventionen für die Benennung der Kopfschutzbügel. Einige Leute nennen es HEADER_2_H_ , andere enthalten den Projektnamen wie MY_PROJECT_HEADER_2_H . Es ist wichtig, sicherzustellen, dass die Konvention, an die Sie sich halten, dafür sorgt, dass jede Datei in Ihrem Projekt über einen eindeutigen Header Guard verfügt.


Wenn die Strukturdetails nicht in der Kopfzeile enthalten wären, wäre der deklarierte Typ unvollständig oder ein undurchsichtiger Typ . Solche Typen können nützlich sein, um Implementierungsdetails vor Benutzern der Funktionen zu verbergen. Für viele Zwecke kann der Typ FILE in der Standard-C-Bibliothek als undurchsichtiger Typ angesehen werden (obwohl er normalerweise nicht undurchsichtig ist, sodass Makroimplementierungen der Standard-E / A-Funktionen die internen Komponenten der Struktur verwenden können). In diesem Fall könnte der header-1.h enthalten:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Beachten Sie, dass die Struktur einen Tag-Namen haben muss (hier MyStruct - das ist im Namespace der Tags getrennt vom gewöhnlichen Bezeichner-Namespace des Typedef-Namens MyStruct ), und dass { … } weggelassen wird. Dies besagt "es gibt einen Strukturtyp struct MyStruct und einen Alias ​​für MyStruct ".

In der Implementierungsdatei können die Details der Struktur definiert werden, um den Typ zu vervollständigen:

struct MyStruct {
    …
};

Wenn Sie C11 verwenden, können Sie die typedef struct MyStruct MyStruct; wiederholen typedef struct MyStruct MyStruct; Deklaration ohne einen Kompilierungsfehler zu verursachen, aber frühere Versionen von C würden sich beschweren. Daher ist es am besten, das Include-Guard-Idiom zu verwenden, obwohl es in diesem Beispiel optional wäre, wenn der Code immer nur mit Compilern kompiliert wurde, die C11 unterstützten.


Viele Compiler unterstützen die #pragma once Direktive, die zu den gleichen Ergebnissen führt:

meine-Header-Datei.h

#pragma once

// Code for header file

#pragma once ist jedoch nicht Teil des C-Standards. #pragma once ist der Code weniger portierbar, wenn Sie ihn verwenden.


Einige Header verwenden nicht das Include-Guard-Idiom. Ein konkretes Beispiel ist der Standardheader <assert.h> . Es kann mehrmals in einer einzigen Übersetzungseinheit enthalten sein, und die Auswirkung davon hängt davon ab, ob das Makro NDEBUG bei jeder Aufnahme des Headers definiert wird. Sie haben gelegentlich eine analoge Anforderung. Solche Fälle werden selten sein. Normalerweise sollten Ihre Header durch das Include-Wächter-Idiom geschützt werden.

FOREACH-Implementierung

Wir können auch Makros verwenden, um das Lesen und Schreiben von Code zu erleichtern. Wir können beispielsweise Makros implementieren, um das foreach Konstrukt in C für einige Datenstrukturen wie einfach und doppelt verknüpfte Listen, Warteschlangen usw. zu implementieren.

Hier ist ein kleines Beispiel.

#include <stdio.h>
#include <stdlib.h>

struct LinkedListNode
{
    int data;
    struct LinkedListNode *next;
};

#define FOREACH_LIST(node, list) \
     for (node=list; node; node=node->next)

/* Usage */
int main(void)
{
    struct LinkedListNode *list, **plist = &list, *node;
    int i;

    for (i=0; i<10; i++)
    {
         *plist = malloc(sizeof(struct LinkedListNode));
         (*plist)->data = i;
         (*plist)->next = NULL;
         plist          = &(*plist)->next;
    }

    /* printing the elements here */
    FOREACH_LIST(node, list)
    {
        printf("%d\n", node->data);
    }
}

Sie können eine Standardschnittstelle für solche Datenstrukturen erstellen und eine generische Implementierung von FOREACH schreiben als:

#include <stdio.h>
#include <stdlib.h>

typedef struct CollectionItem_
{
    int data;
    struct CollectionItem_ *next;
} CollectionItem;

typedef struct Collection_
{
    /* interface functions */
    void* (*first)(void *coll);
    void* (*last) (void *coll);
    void* (*next) (void *coll, CollectionItem *currItem);

    CollectionItem *collectionHead;
    /* Other fields */
} Collection;

/* must implement */
void *first(void *coll)
{
    return ((Collection*)coll)->collectionHead;
}

/* must implement */
void *last(void *coll)
{
    return NULL;
}

/* must implement */
void *next(void *coll, CollectionItem *curr)
{
    return curr->next;
}

CollectionItem *new_CollectionItem(int data)
{
    CollectionItem *item = malloc(sizeof(CollectionItem));
    item->data = data;
    item->next = NULL;
    return item;
}

void Add_Collection(Collection *coll, int data)
{
    CollectionItem **item = &coll->collectionHead;
    while(*item)
        item = &(*item)->next;
    (*item) = new_CollectionItem(data);
}

Collection *new_Collection()
{
    Collection *nc = malloc(sizeof(Collection));
    nc->first = first;
    nc->last  = last;
    nc->next  = next;
    return nc;
}

/* generic implementation */
#define FOREACH(node, collection)                      \
    for (node  = (collection)->first(collection);      \
         node != (collection)->last(collection);       \
         node  = (collection)->next(collection, node))

int main(void)
{
    Collection *coll = new_Collection();
    CollectionItem *node;
    int i;

    for(i=0; i<10; i++)
    {
         Add_Collection(coll, i);
    }

    /* printing the elements here */
    FOREACH(node, coll)
    {
        printf("%d\n", node->data);
    }
}

Um diese generische Implementierung zu verwenden, implementieren Sie diese Funktionen einfach für Ihre Datenstruktur.

1.  void* (*first)(void *coll);
2.  void* (*last) (void *coll);
3.  void* (*next) (void *coll, CollectionItem *currItem);

__cplusplus für die Verwendung von C-Externen in C ++ - Code, zusammengestellt mit C ++ - Namensverstümmelung

Es gibt Zeiten, in denen eine Include-Datei unterschiedliche Ausgaben vom Präprozessor erzeugen muss, abhängig davon, ob der Compiler aufgrund von Sprachunterschieden ein C-Compiler oder ein C ++ - Compiler ist.

Beispielsweise ist eine Funktion oder eine andere externe in einer C-Quelldatei definiert, wird jedoch in einer C ++ - Quelldatei verwendet. Da C ++ die Namensveränderung (oder Namensdekoration) verwendet, um eindeutige Funktionsnamen basierend auf Funktionsargumenttypen zu generieren, führt eine in einer C ++ - Quelldatei verwendete Deklaration der C-Funktion zu Verbindungsfehlern. Der C ++ - Compiler ändert den angegebenen externen Namen für die Compiler-Ausgabe unter Verwendung der Regeln zur Namensänderung für C ++. Das Ergebnis sind Verbindungsfehler aufgrund von Externen, die nicht gefunden wurden, wenn die C ++ - Compilerausgabe mit der C-Compilerausgabe verknüpft ist.

Da C-Compiler keine Namensänderung vornehmen, C ++ - Compiler jedoch für alle vom C ++ - Compiler generierten externen Labels (Funktionsnamen oder Variablennamen), wurde ein vordefiniertes Präprozessor-Makro, __cplusplus , eingeführt, um die Compiler-Erkennung zu ermöglichen.

Um dieses Problem der inkompatiblen Compilerausgabe für externe Namen zwischen C und C ++ zu __cplusplus ist das Makro __cplusplus im C ++ - Präprozessor definiert und nicht im C-Präprozessor definiert. Dieser #ifdef kann mit der bedingten Präprozessor-Direktive #ifdef oder #if mit dem Operator defined() werden, um festzustellen, ob eine Quellcode- oder Include-Datei als C ++ oder C kompiliert wird.

#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif

Oder du könntest es gebrauchen

#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif

Um den korrekten Funktionsnamen einer Funktion aus einer C-Quelldatei anzugeben, die mit dem C-Compiler kompiliert wurde, der in einer C ++ - Quelldatei verwendet wird, können Sie nach der definierten Konstante __cplusplus , um das extern "C" { /* ... */ }; zur Deklaration von C externals, wenn die Headerdatei in einer C ++ - Quelldatei enthalten ist. Beim Kompilieren mit einem C-Compiler wird jedoch das extern "C" { */ ... */ }; ist nicht benutzt. Diese bedingte Kompilierung ist erforderlich, da extern "C" { /* ... */ }; ist in C ++ gültig, aber nicht in C.

#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif

// exported C function list.
int foo (void);

#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif

Funktionsartige Makros

Funktionsähnliche Makros ähneln inline Funktionen. Diese sind in einigen Fällen nützlich, beispielsweise im temporären Debug-Protokoll:

#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"

# define LOG(str) do {                            \
  FILE *fp = fopen(LOGFILENAME, "a");            \
  if (fp) {                                       \
    fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
                 /* don't print null pointer */   \
                 str ?str :"<null>");             \
    fclose(fp);                                   \
  }                                               \
  else {                                          \
    perror("Opening '" LOGFILENAME "' failed");   \
  }                                               \
} while (0)
#else
  /* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif


#include <stdio.h>

int main(int argc, char* argv[])
{
    if (argc > 1)
        LOG("There are command line arguments");
    else
        LOG("No command line arguments");
    return 0;
}

In beiden Fällen verhält sich der Aufruf in beiden Fällen (mit DEBUG oder nicht) wie eine Funktion mit void Rückgabetyp. Dadurch wird sichergestellt, dass die if/else Bedingungen wie erwartet interpretiert werden.

Im DEBUG Fall wird dies durch ein do { ... } while(0) DEBUG implementiert. Im anderen Fall ist (void)0 eine Anweisung ohne Nebeneffekt, die einfach ignoriert wird.

Eine Alternative für Letzteres wäre

#define LOG(LINE) do { /* empty */ } while (0)

so, dass es in allen Fällen dem ersten syntaktisch entspricht.

Wenn Sie GCC verwenden, können Sie auch ein funktionsähnliches Makro implementieren, das das Ergebnis mit nicht standardmäßigen Ausdrücken der GNU-Erweiterungsanweisung zurückgibt. Zum Beispiel:

#include <stdio.h>

#define POW(X, Y) \
({ \
        int i, r = 1; \
        for (i = 0; i < Y; ++i) \
                r *= X; \
        r; \ // returned value is result of last operation
})

int main(void)
{
        int result;

        result = POW(2, 3); 
        printf("Result: %d\n", result);
}

Variadische Argumente Makro

C99

Makros mit variablen Args:

Angenommen, Sie möchten ein Druckmakro erstellen, um Ihren Code zu debuggen. Nehmen wir dieses Makro als Beispiel:

#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)

Einige Anwendungsbeispiele:

Die Funktion somefunc() gibt -1 zurück, wenn sie nicht bestanden hat, und 0, wenn sie erfolgreich war, und sie wird an vielen Stellen im Code aufgerufen:

int retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

/* some other code */

 retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

Was passiert, wenn sich die Implementierung von somefunc() ändert und jetzt unterschiedliche Werte zurückgibt, die auf verschiedene mögliche Fehlertypen abgestimmt sind? Sie möchten weiterhin das Debug-Makro verwenden und den Fehlerwert drucken.

debug_printf(retVal);      /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */

Um dieses Problem zu lösen, wurde das Makro __VA_ARGS__ eingeführt. Dieses Makro erlaubt mehrere Parameter von X-Makros:

Beispiel:

 #define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
                               printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)

Verwendungszweck:

int retVal = somefunc();

debug_print("retVal of somefunc() is-> %d", retVal);

Mit diesem Makro können Sie mehrere Parameter übergeben und drucken. Jetzt können Sie jedoch keine Parameter mehr senden.

debug_print("Hey");

Dies würde zu einem Syntaxfehler führen, da das Makro mindestens ein weiteres Argument erwartet und der Vorprozessor das fehlende Komma im Makro debug_print() nicht ignoriert. Auch debug_print("Hey",); Ein Syntaxfehler wird ausgelöst, da das an Makro übergebene Argument nicht leer bleiben kann.

Um dieses ##__VA_ARGS__ zu lösen, wurde das ##__VA_ARGS__ Makro eingeführt. Dieses Makro besagt, dass das Komma, wenn keine variablen Argumente vorhanden sind, vom Vorprozessor aus dem Code gelöscht wird.

Beispiel:

 #define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
                               printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)

Verwendungszweck:

 debug_print("Ret val of somefunc()?");
 debug_print("%d",somefunc());


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow