Sök…


Introduktion

Alla preprocessorkommandon börjar med hash (pund) symbolen # . AC-makro är bara ett preprocessor-kommando som definieras med #define preprocessor-direktivet. Under förbehandlingssteget ersätter C-förbehandlaren (en del av C-kompilatorn) helt enkelt makroorganet varhelst dess namn visas.

Anmärkningar

När en kompilator stöter på ett makro i koden utför den enkel strängbyte, inga ytterligare åtgärder utförs. På grund av detta respekterar förändringar av förprocessorn inte omfattningen av C-program - till exempel är en makrodefinition inte begränsad till att vara inom ett block, så det påverkas inte av ett '}' som slutar ett blockuttal.

Förbehandlaren är, genom design, inte turing komplett - det finns flera typer av beräkning som inte kan göras av förbehandlaren ensam.

Vanligtvis har kompilatorer en kommandoradsflagg (eller konfigurationsinställning) som gör att vi kan stoppa kompilering efter förberedelsefasen och kontrollera resultatet. På POSIX-plattformar är denna flagga -E . Så att köra gcc med den här flaggan skriver ut den utvidgade källan till stdout:

$ gcc -E cprog.c

Ofta implementeras förbehandlaren som ett separat program, som åberopas av kompilatorn, det vanliga namnet för det programmet är cpp . Ett antal förbehandlare avger stödinformation, till exempel information om radnummer - som används i efterföljande faser av sammanställningen för att generera felsökningsinformation. Om förbehandlaren är baserad på gcc undertrycker alternativet -P sådan information.

$ cpp -P cprog.c

Villkorligt införande och modifiering av villkorad funktionssignatur

För att villkorligen inkludera ett kodblock har förbehandlaren flera direktiv (t.ex. #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

Normala C-relationsoperatörer kan användas för #if villkoret

#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

#if direktiven uppträder liknar C if uttalande, det ska endast innehålla integrerade konstant uttryck och inga avkastningar. Den stöder ytterligare en unär operatör, defined( identifier ) , som returnerar 1 om identifieraren är definierad, och 0 annars.

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

Villkorlig funktion Signaturändring

I de flesta fall förväntas en frigöringsbyggnad av en applikation ha så lite omkostnad som möjligt. Under testning av en interimsuppbyggnad kan dock ytterligare loggar och information om hittade problem vara till hjälp.

Antag till exempel att det finns någon funktion SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) som när man gör en testbyggnad är önskad kommer att generera en logg om dess användning. Men denna funktion används på flera platser och det är önskvärt att när man skapar loggen, är en del av informationen att veta varifrån är funktionen anropas.

Så genom att använda villkorad sammanställning kan du ha något liknande följande i inkluderingsfilen som förklarar funktionen. Detta ersätter standardversionen av funktionen med en felsökningsversion av funktionen. Förprocessorn används för att ersätta samtal till funktionen SerOpPluAllRead() med samtal till funktionen SerOpPluAllRead_Debug() med ytterligare två argument, namnet på filen och radnumret där funktionen används.

Villkorlig sammanställning används för att välja om åsidosättande av standardfunktionen med en felsökningsversion eller inte.

#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

Detta gör att du kan åsidosätta standardversionen av funktionen SerOpPluAllRead() med en version som kommer att ange namnet på filen och radnumret i filen där funktionen heter.

Det finns ett viktigt övervägande: alla filer som använder den här funktionen måste innehålla rubrikfilen där den här metoden används för att förbehandlaren ska modifiera funktionen. Annars ser du ett länkfel.

Definitionen av funktionen ser ut som följande. Vad den här källan gör är att begära att förprocessorn byter namn på funktionen SerOpPluAllRead() att vara SerOpPluAllRead_Debug() och att ändra argumentlistan så att den innehåller två ytterligare argument, en pekare till namnet på filen där funktionen kallades och radnumret i filen där funktionen används.

#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;
}

Inkludering av källfil

De vanligaste användningarna av #include förbehandlingsdirektiv är som i följande:

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

#include ersätter uttalandet med innehållet i den fil som det hänvisas till. Vinkelfästen (<>) hänvisar till rubrikfiler som är installerade i systemet medan citattecken ("") är avsedda för användare som levereras.

Makron själva kan utvidga andra makron en gång, som det här exemplet illustrerar:

#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

Makroersättning

Den enklaste formen för makroersättning är att definiera en manifest constant , som i

#define ARRSIZE 100
int array[ARRSIZE];

Detta definierar ett funktionsliknande makro som multiplicerar en variabel med 10 och lagrar det nya värdet:

#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);

Ersättningen görs innan någon annan tolkning av programtexten. I det första samtalet till TIMES10 namnet A från definitionen av b och den så utvidgade texten sätts sedan i stället för samtalet. Observera att denna definition av TIMES10 inte motsvarar

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

eftersom detta kan utvärdera ersättningen av A två gånger, vilket kan ha oönskade biverkningar.

Följande definierar ett funktionsliknande makro vilket värde är maximalt för dess argument. Det har fördelarna med att arbeta för alla kompatibla typer av argument och att generera in-line-kod utan funktionssamtal. Det har nackdelarna med att utvärdera en eller annan av sina argument en andra gång (inklusive biverkningar) och att generera mer kod än en funktion om den åberopas flera gånger.

#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 */

På grund av detta undviks vanligtvis sådana makron som utvärderar deras argument flera gånger i produktionskod. Sedan C11 finns det _Generic funktion som gör det möjligt att undvika sådana flera invokationer.

De överflödiga parenteserna i makroutvidgningarna (högra sidan av definitionen) säkerställer att argumenten och det resulterande uttrycket binds ordentligt och passar väl in i det sammanhang som makroen heter.

Feldirektiv

Om förbehandlaren möter ett #error direktiv #error sammanställningen och det medföljande diagnostiska meddelandet skrivs ut.

#define DEBUG

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

int main(void) {
    return 0;
}

Möjlig utgång:

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

# om 0 för att blockera kodavsnitt

Om det finns delar av koden som du funderar på att ta bort eller vill avaktivera tillfälligt kan du kommentera den med en blockkommentar.

/* 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;
}
*/

Men om källkoden du har omgiven med en blockkommentar har blockstilkommentarer i källan, kan avslutningen * / av befintliga blockkommentarer orsaka att din nya blockkommentar blir ogiltig och orsakar kompilationsproblem.

/* 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;
}
*/ 

I föregående exempel ses de två sista linjerna i funktionen och den sista '* /' av kompilatorn, så den skulle kompileras med fel. En säkrare metod är att använda ett #if 0 direktiv kring koden du vill blockera.

#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

En fördel med detta är att när du vill gå tillbaka och hitta koden är det mycket lättare att söka efter "#if 0" än att söka i alla dina kommentarer.

En annan mycket viktig fördel är att du kan #if 0 bo för att kommentera kod med #if 0 . Detta kan inte göras med kommentarer.

Ett alternativ till att använda #if 0 är att använda ett namn som inte kommer att #defined men är mer beskrivande för varför koden blockeras. Om det till exempel finns en funktion som verkar vara värdelös död kod kan du använda #if defined(POSSIBLE_DEAD_CODE) eller #if defined(FUTURE_CODE_REL_020201) för kod som behövs när annan funktionalitet är på plats eller något liknande. När du sedan går igenom för att ta bort eller aktivera den källan, är källsektionerna lätt att hitta.

Klistra in token

Klistra in klistermärken gör att man kan limma in två makroargument. Till exempel ger front##back frontback . Ett känt exempel är Win32s <TCHAR.H> rubrik. I standard C kan man skriva L"string" att förklara en bred teckensträng. Men Windows API tillåter en att konvertera mellan breda teckensträngar och smala teckensträngar helt enkelt genom att #define UNICODE . För att implementera TCHAR.H använder TCHAR.H detta

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

När en användare skriver TEXT("hello, world") och UNICODE definieras, sammanför C-förbehandlaren L och makroargumentet. L sammankopplad med "hello, world" ger L"hello, world" .

Fördefinierade makroer

Ett fördefinierat makro är ett makro som redan förstås av C pre-processorn utan att programmet behöver definiera det. Exempel inkluderar

Obligatoriska fördefinierade makroer

  • __FILE__ , som ger filnamnet på den aktuella källfilen (en strängbokstav),
  • __LINE__ för det aktuella __LINE__ (en heltalskonstant),
  • __DATE__ för sammanställningsdatumet (en strängbokstav),
  • __TIME__ för sammanställningstiden (en strängbokstav).

Det finns också en relaterad fördefinierad identifierare, __func__ (ISO / IEC 9899: 2011 §6.4.2.2), som inte är ett makro:

Identifieraren __func__ ska implicit förklaras av översättaren som om, omedelbart efter öppningsstödet för varje funktionsdefinition, deklarationen:

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

dök upp, där funktionsnamn är namnet på den lexiskt inneslutna funktionen.

__FILE__ , __LINE__ och __func__ är särskilt användbara för felsökning. Till exempel:

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

Pre-C99-kompilatorer, kan kanske inte stöder __func__ eller kan ha ett makro som fungerar på samma sätt som heter annorlunda. Till exempel använde gcc __FUNCTION__ i C89-läge.

Nedanstående makron tillåter att be om detaljer om implementeringen:

  • __STDC_VERSION__ Den version av C-standarden som implementerats. Detta är ett konstant heltal med formatet yyyymmL (värdet 201112L för C11, värdet 199901L för C99; det definierades inte för C89 / C90)
  • __STDC_HOSTED__ 1 om det är en värd implementering, annars 0 .
  • __STDC__ Om 1 överensstämmer implementeringen med C-standarden.

Andra fördefinerade makron (ej obligatoriska)

ISO / IEC 9899: 2011 §6.10.9.2 Miljömakron:

  • __STDC_ISO_10646__ En heltalskonstant i formen yyyymmL (till exempel 199712L). Om denna symbol definieras, har varje tecken i Unicode-uppsättningen, som lagras i ett objekt av typen wchar_t , samma värde som den korta identifieraren för det tecknet. Unicode-uppsättningen består av alla tecken som definieras av ISO / IEC 10646, tillsammans med alla ändringar och tekniska rättelser från det angivna året och månaden. Om någon annan kodning används ska makroen inte definieras och den faktiska kodningen som används är implementeringsdefinerad.

  • __STDC_MB_MIGHT_NEQ_WC__ Heltalskonstanten 1, avsedd att indikera att en kod i kodningen för wchar_t inte behöver ha ett kodvärde lika med dess värde när det används som ensamtecken i en heltalsteckenkonstant.

  • __STDC_UTF_16__ Heltalskonstanten 1, avsedd att indikera att värdena av typen char16_t är UTF − 16-kodade. Om någon annan kodning används ska makroen inte definieras och den faktiska kodningen som används är implementeringsdefinerad.

  • __STDC_UTF_32__ Heltalskonstanten 1, avsedd att indikera att värdena av typen char32_t är UTF − 32-kodade. Om någon annan kodning används ska makroen inte definieras och den faktiska kodningen som används är implementeringsdefinerad.

ISO / IEC 9899: 2011 §6.10.8.3 Villkorliga funktionsmakroer

  • __STDC_ANALYZABLE__ Heltalskonstanten 1, avsedd att indikera överensstämmelse med specifikationerna i bilaga L (Analysbarhet).
  • __STDC_IEC_559__ Heltalskonstanten 1, avsedd att indikera överensstämmelse med specifikationerna i bilaga F (IEC 60559 flytande punkt aritmetik).
  • __STDC_IEC_559_COMPLEX__ Heltalskonstanten 1, avsedd att indikera efterlevnaden av specifikationerna i bilaga G (IEC 60559 kompatibel komplex aritmetik).
  • __STDC_LIB_EXT1__ Heltalskonstanten 201112L , avsedd att indikera stöd för tillägg som definieras i bilaga K (gränssnittsgränssnittsgränssnitt).
  • __STDC_NO_ATOMICS__ Heltalskonstanten 1, avsedd att indikera att implementeringen inte stöder atomtyper (inklusive kvalificeringen _Atomic type) och <stdatomic.h> rubriken.
  • __STDC_NO_COMPLEX__ Heltalskonstanten 1, avsedd att indikera att implementeringen inte stöder komplexa typer eller <complex.h> .
  • __STDC_NO_THREADS__ Heltalskonstanten 1, avsedd att indikera att implementeringen inte stöder rubriken <threads.h> .
  • __STDC_NO_VLA__ Heltalskonstanten 1, avsedd att indikera att implementeringen inte stöder matriser med variabel längd eller variabelt modifierade typer.

Header Inkludera vakter

Ganska mycket varje rubrikfil bör följa inkluderande skyddsidiom:

my-header-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Detta säkerställer att när du #include "my-header-file.h" på flera platser, inte får du duplicerade deklarationer av funktioner, variabler etc. Föreställ dig följande hierarki med filer:

header-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

header-2 hi

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

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

int main() {
    // do something
}

Den här koden har ett allvarligt problem: det detaljerade innehållet i MyStruct definieras två gånger, vilket inte är tillåtet. Detta skulle resultera i ett sammanställningsfel som kan vara svårt att spåra, eftersom en huvudfil innehåller en annan. Om du istället gjorde det med rubrikvakter:

header-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

header-2 hi

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c

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

int main() {
    // do something
}

Detta skulle sedan utvidgas till:

#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
}

När kompilatorn når den andra inkluderingen av rubrik-1.h , HEADER_1_H definierades redan av den tidigare inkluderingen. Ergo, det kommer till följande:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

Och därmed finns det inget sammanställningsfel.

Obs: Det finns flera olika konventioner för att namnge rubrikskydd. Vissa människor gillar att namnge det HEADER_2_H_ , vissa inkluderar projektnamnet som MY_PROJECT_HEADER_2_H . Det viktiga är att se till att konventet du följer gör det så att varje fil i ditt projekt har en unik rubrikvakt.


Om strukturinformationen inte ingick i rubriken skulle den deklarerade typen vara ofullständig eller en ogenomskinlig typ . Sådana typer kan vara användbara och dölja implementeringsdetaljer från användare av funktionerna. I många syften kan FILE typen i standard C-biblioteket betraktas som en ogenomskinlig typ (även om den vanligtvis inte är ogenomskinlig så att makroimplementeringar av standard I / O-funktionerna kan utnyttja interna strukturer). I så fall kan header-1.h innehålla:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Observera att strukturen måste ha ett taggnamn (här MyStruct - det finns i taggens namnutrymme, separat från det vanliga identifieringsnamnområdet för typedef-namnet MyStruct ), och att { … } utelämnas. Detta säger "det finns en strukturtyp struct MyStruct och det finns ett alias för det MyStruct ".

I implementeringsfilen kan detaljerna i strukturen definieras för att göra typen komplett:

struct MyStruct {
    …
};

Om du använder C11 kan du upprepa typedef struct MyStruct MyStruct; deklaration utan att orsaka ett sammanställningsfel, men tidigare versioner av C skulle klaga. Följaktligen är det fortfarande bäst att använda inkluderande skyddsidiom, även om det i detta exempel skulle vara valfritt om koden bara någonsin hade kompilerats med kompilatorer som stöder C11.


Många kompilatorer stöder #pragma once direktivet en #pragma once , vilket har samma resultat:

my-header-file.h

#pragma once

// Code for header file

Dock är #pragma once inte en del av C-standarden, så koden är mindre bärbar om du använder den.


Några rubriker använder inte inkluderande skyddsidiom. Ett specifikt exempel är standard <assert.h> rubriken. Det kan inkluderas flera gånger i en enda översättningsenhet, och effekten av att göra det beror på om makro NDEBUG definieras varje gång rubriken inkluderas. Du kan ibland ha ett liknande krav; sådana fall kommer att vara få och långt mellan. Vanligtvis bör dina rubriker skyddas av inkluderande skyddsidiom.

FOREACH implementering

Vi kan också använda makron för att göra koden lättare att läsa och skriva. Vi kan till exempel implementera makroer för implementering av foreach konstruktionen i C för vissa datastrukturer som singel- och dubbelkopplade listor, köer etc.

Här är ett litet exempel.

#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);
    }
}

Du kan skapa ett standardgränssnitt för sådana datastrukturer och skriva en generisk implementering av FOREACH som:

#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);
    }
}

Om du vill använda den här generiska implementeringen implementerar du bara dessa funktioner för din datastruktur.

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

__cplusplus för att använda C-externer i C ++ -kod sammanställd med C ++ - namngling

Det finns tillfällen då en inkluderingsfil måste generera olika utmatningar från förbehandlaren beroende på om kompilatorn är en C-kompilator eller en C ++ -kompilator på grund av språkskillnader.

Till exempel definieras en funktion eller annan extern i en C-källfil men används i en C ++ källfil. Eftersom C ++ använder namngling (eller namndekoration) för att generera unika funktionsnamn baserat på funktionsargumenttyper orsakar en C-funktionsdeklaration som används i en C ++ källfil en länkfel. C ++ -kompileraren kommer att modifiera det angivna externa namnet för kompilatorutgången med hjälp av namnmanglingsreglerna för C ++. Resultatet är länkfel på grund av externa enheter som inte hittas när C ++ -kompilatorutgången är länkad till C-kompilatorutgången.

Eftersom C-kompilatorer inte namnger mangling men C ++ -kompilatorer gör för alla externa etiketter (funktionsnamn eller variabla namn) genererade av C ++ -kompilatorn, infördes ett fördefinierat förbehandlingsmakro, __cplusplus , för att möjliggöra kompilatordetektering.

För att lösa problemet med inkompatibel kompilatorutgång för externa namn mellan C och C ++ definieras makro __cplusplus i C ++ förprocessorn och definieras inte i C förprocessorn. Detta makronamn kan användas med villkorat #ifdef direktiv eller #if med den defined() operatören för att berätta om en källkod eller inkluderingsfil kompileras som C ++ eller C.

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

Eller så kan du använda

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

För att ange rätt funktionsnamn för en funktion från en C-källfil som är sammanställd med C-kompilatorn som används i en C ++ källfil kan du kontrollera om __cplusplus definierade konstanten för att orsaka extern "C" { /* ... */ }; som ska användas för att deklarera C-externer när rubrikfilen ingår i en C ++ källfil. Men när den är kompilerad med en C-kompilator, är extern "C" { */ ... */ }; används inte. Denna villkorliga sammanställning behövs eftersom extern "C" { /* ... */ }; är giltig i C ++ men inte i 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

Funktionsliknande makron

Funktionsliknande makroer liknar inline , dessa är användbara i vissa fall, till exempel tillfällig felsökningslogg:

#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;
}

Här i båda fallen (med DEBUG eller inte) uppträder samtalet på samma sätt som en funktion med void återgångstyp. Detta säkerställer att if/else konditionerna tolkas som förväntat.

I DEBUG fallet implementeras detta genom en do { ... } while(0) konstruktion. I det andra fallet är (void)0 ett uttalande utan bieffekt som bara ignoreras.

Ett alternativ för det senare skulle vara

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

så att det i alla fall är syntaktiskt motsvarande det första.

Om du använder GCC kan du också implementera ett funktionsliknande makro som returnerar resultat med en icke-standard GNU-förlängning - uttalande . Till exempel:

#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);
}

Variadiska argument makro

C99

Makron med variadiska arg:

Låt oss säga att du vill skapa ett tryckmakro för felsökning av din kod, låt oss ta detta makro som ett exempel:

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

Några exempel på användning:

Funktionen somefunc() returnerar -1 om den misslyckades och 0 om den lyckades, och den kallas från många olika platser i koden:

int retVal = somefunc();

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

/* some other code */

 retVal = somefunc();

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

Vad händer om implementeringen av somefunc() ändras och nu returnerar olika värden som matchar olika möjliga feltyper? Du vill fortfarande använda felsökningsmakro och skriva ut felvärdet.

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

För att lösa detta problem __VA_ARGS__ . Detta makro tillåter flera parametrar X-makro:

Exempel:

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

Användande:

int retVal = somefunc();

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

Detta makro låter dig skicka flera parametrar och skriva ut dem, men nu förbjuder det dig att skicka några parametrar alls.

debug_print("Hey");

Detta skulle höja vissa syntaxfel eftersom makroen förväntar sig åtminstone ytterligare ett argument och förprocessorn inte skulle ignorera bristen på komma i debug_print() . debug_print("Hey",); skulle höja ett syntaxfel när du inte kan hålla argumentet skickat till makro tomt.

För att lösa detta ##__VA_ARGS__ makro, detta makro säger att om inga variabla argument finns raderas komma av förprocessorn från koden.

Exempel:

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

Användande:

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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow