Ricerca…


introduzione

Tutti i comandi del preprocessore iniziano con il simbolo # cancelletto (cancelletto). La macro AC è solo un comando del preprocessore che viene definito utilizzando la direttiva #define preprocessore. Durante la fase di pre-elaborazione, il preprocessore C (una parte del compilatore C) sostituisce semplicemente il corpo della macro ovunque appaia il suo nome.

Osservazioni

Quando un compilatore incontra una macro nel codice, esegue la sostituzione semplice della stringa, senza eseguire ulteriori operazioni. Per questo motivo, le modifiche apportate dal preprocessore non rispettano l'ambito dei programmi C: ad esempio, una definizione di macro non è limitata all'essere all'interno di un blocco, quindi non è influenzata da un '}' che termina un'istruzione di blocco.

Il preprocessore è, per impostazione, non completo - esistono diversi tipi di calcolo che non possono essere eseguiti dal solo preprocessore.

Di solito i compilatori hanno un flag a riga di comando (o impostazione di configurazione) che ci consente di interrompere la compilazione dopo la fase di pre-elaborazione e di ispezionare il risultato. Su piattaforme POSIX questo flag è -E . Quindi, l'esecuzione di gcc con questo flag stampa il codice sorgente espanso su stdout:

$ gcc -E cprog.c

Spesso il preprocessore viene implementato come un programma separato, che viene richiamato dal compilatore, il nome comune per quel programma è cpp . Un numero di preprocessori emette informazioni di supporto, come informazioni sui numeri di riga, che viene utilizzato dalle fasi successive della compilazione per generare informazioni di debug. Nel caso in cui il preprocessore sia basato su gcc, l'opzione -P sopprime tali informazioni.

$ cpp -P cprog.c

Inclusione condizionale e modifica della firma della funzione condizionale

Per includere condizionalmente un blocco di codice, il preprocessore ha diverse direttive (ad esempio #if , #ifdef , #else , #endif , ecc.).

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

Gli operatori relazionali C normali possono essere utilizzati per la condizione #if

#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

Le direttive #if si comportano in modo analogo all'istruzione C if , devono contenere solo espressioni costanti integrali e nessun cast. Supporta un operatore unario aggiuntivo, defined( identifier ) , che restituisce 1 se l'identificatore è definito e 0 altrimenti.

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

Modifica firma funzione condizionale

Nella maggior parte dei casi, si prevede che il rilascio di build di un'applicazione abbia il minor overhead possibile. Tuttavia, durante il test di una build provvisoria, possono essere utili log aggiuntivi e informazioni sui problemi rilevati.

Ad esempio, supponiamo che ci sia una qualche funzione SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) che quando si esegue una build di test è necessario generare un log relativo al suo utilizzo. Tuttavia questa funzione viene utilizzata in più punti e si desidera che quando si genera il registro, parte delle informazioni sia sapere da dove viene chiamata la funzione.

Pertanto, utilizzando la compilazione condizionale è possibile avere qualcosa di simile al seguente nel file di inclusione che dichiara la funzione. Sostituisce la versione standard della funzione con una versione di debug della funzione. Il preprocessore viene utilizzato per sostituire le chiamate alla funzione SerOpPluAllRead() con le chiamate alla funzione SerOpPluAllRead_Debug() con due argomenti aggiuntivi, il nome del file e il numero di riga di dove viene utilizzata la funzione.

La compilazione condizionale viene utilizzata per scegliere se sovrascrivere la funzione standard con una versione di debug o meno.

#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

Ciò consente di sostituire la versione standard della funzione SerOpPluAllRead() con una versione che fornirà il nome del file e il numero di riga nel file in cui viene chiamata la funzione.

C'è una considerazione importante: qualsiasi file che utilizza questa funzione deve includere il file di intestazione in cui viene utilizzato questo approccio in modo che il preprocessore possa modificare la funzione. Altrimenti vedrai un errore del linker.

La definizione della funzione sarebbe simile alla seguente. Ciò che fa questa fonte è richiedere che il preprocessore rinomina la funzione SerOpPluAllRead() in SerOpPluAllRead_Debug() e che modifichi l'elenco degli argomenti per includere due argomenti aggiuntivi, un puntatore al nome del file in cui è stata chiamata la funzione e il numero di riga nel file in cui viene utilizzata la funzione.

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

Inclusione del file di origine

Gli usi più comuni delle direttive di #include pre-elaborazione sono i seguenti:

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

#include sostituisce la dichiarazione con il contenuto del file a cui si fa riferimento. Le parentesi angolari (<>) si riferiscono ai file di intestazione installati sul sistema, mentre le virgolette ("") sono per i file forniti dall'utente.

Le macro stesse possono espandere altre macro una volta, come questo esempio illustra:

#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

Sostituzione macro

La forma più semplice di sostituzione macro è definire una manifest constant , come in

#define ARRSIZE 100
int array[ARRSIZE];

Definisce una macro simile a una funzione che moltiplica una variabile per 10 e memorizza il nuovo valore:

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

La sostituzione viene eseguita prima di qualsiasi altra interpretazione del testo del programma. Nella prima chiamata a TIMES10 il nome A della definizione viene sostituito da b e il testo espanso viene quindi inserito al posto della chiamata. Si noti che questa definizione di TIMES10 non è equivalente a

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

perché questo potrebbe valutare la sostituzione di A , due volte, che può avere effetti collaterali indesiderati.

Quanto segue definisce una macro simile a una funzione il cui valore è il massimo dei suoi argomenti. Ha i vantaggi di lavorare per qualsiasi tipo compatibile di argomenti e di generare codice in linea senza il sovraccarico delle chiamate di funzione. Ha gli svantaggi di valutare l'uno o l'altro dei suoi argomenti una seconda volta (compresi gli effetti collaterali) e di generare più codice di una funzione se invocato più volte.

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

Per questo motivo, tali macro che valutano i loro argomenti più volte vengono solitamente evitate nel codice di produzione. Dal momento che C11 c'è la funzione _Generic che consente di evitare tali invocazioni multiple.

Le abbondanti parentesi nelle macro espansioni (lato destro della definizione) assicurano che gli argomenti e l'espressione risultante siano collegati correttamente e si adattino bene al contesto in cui viene chiamata la macro.

Direttiva di errore

Se il preprocessore incontra una direttiva #error , la compilazione viene interrotta e viene stampato il messaggio diagnostico.

#define DEBUG

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

int main(void) {
    return 0;
}

Uscita possibile:

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

#if 0 per bloccare le sezioni di codice

Se ci sono sezioni di codice che stai considerando di rimuovere o disabilitare temporaneamente, puoi commentare con un commento di blocco.

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

Tuttavia, se il codice sorgente che hai circondato con un commento a blocchi ha commenti di stile bloccati nell'origine, il finale * / dei commenti di blocco esistenti può causare il tuo nuovo commento di blocco non valido e causare problemi di compilazione.

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

Nell'esempio precedente, le ultime due righe della funzione e l'ultimo '* /' sono viste dal compilatore, quindi compilare con errori. Un metodo più sicuro è usare una direttiva #if 0 attorno al codice che vuoi bloccare.

#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

Un vantaggio è che quando si desidera tornare indietro e trovare il codice, è molto più semplice eseguire una ricerca per "#if 0" rispetto alla ricerca di tutti i commenti.

Un altro vantaggio molto importante è che è possibile nidificare commentando il codice con #if 0 . Questo non può essere fatto con i commenti.

Un'alternativa all'utilizzo di #if 0 consiste nell'utilizzare un nome che non sarà #defined ma che è più descrittivo del motivo per cui il codice viene bloccato. Ad esempio, se esiste una funzione che sembra essere un codice morto inutile, è possibile utilizzare #if defined(POSSIBLE_DEAD_CODE) o #if defined(FUTURE_CODE_REL_020201) per il codice necessario una volta che sono state #if defined(FUTURE_CODE_REL_020201) altre funzionalità o qualcosa di simile. Quindi, quando si torna indietro per rimuovere o abilitare tale fonte, quelle sezioni di origine sono facili da trovare.

Incollare token

L'incollaggio di token consente di incollare insieme due argomenti macro. Ad esempio, il front##back restituisce il frontback . Un esempio famoso è l'intestazione <TCHAR.H> di Win32. Nella C standard, si può scrivere L"string" per dichiarare una stringa di caratteri ampia. Tuttavia, l'API di Windows consente di convertire tra stringhe di caratteri estesi e stringhe di caratteri strette semplicemente con #define UNICODE . Per implementare i letterali stringa, TCHAR.H utilizza questo

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

Ogni volta che un utente scrive TEXT("hello, world") e UNICODE è definito, il preprocessore C concatena L e l'argomento macro. L concatenato con "hello, world"L"hello, world" .

Macro predefinite

Una macro predefinita è una macro già compresa dal pre processore C senza che il programma debba definirla. Esempi inclusi

Macro predefinite obbligatorie

  • __FILE__ , che fornisce il nome file del file sorgente corrente (una stringa letterale),
  • __LINE__ per il numero di riga corrente (una costante intera),
  • __DATE__ per la data di compilazione (una stringa letterale),
  • __TIME__ per il tempo di compilazione (una stringa letterale).

C'è anche un identificatore predefinito correlato, __func__ (ISO / IEC 9899: 2011 §6.4.2.2), che non è una macro:

L'identificatore __func__ deve essere implicitamente dichiarato dal traduttore come se, immediatamente dopo la parentesi di apertura di ogni definizione di funzione, la dichiarazione:

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

è apparso, dove nome-funzione è il nome della funzione che include lessicamente.

__FILE__ , __LINE__ e __func__ sono particolarmente utili per scopi di debug. Per esempio:

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

I compilatori Pre-C99, possono o non possono supportare __func__ o possono avere una macro che agisce come denominata in modo diverso. Ad esempio, gcc ha utilizzato __FUNCTION__ in modalità C89.

I seguenti macro consentono di chiedere dettagli sull'implementazione:

  • __STDC_VERSION__ La versione dello standard C implementata. Questo è un numero intero costante che utilizza il formato yyyymmL (il valore 201112L per C11, il valore 199901L per C99, non definito per C89 / C90)
  • __STDC_HOSTED__ 1 se si tratta di un'implementazione ospitata, altrimenti 0 .
  • __STDC__ Se 1 , l'implementazione è conforme allo standard C.

Altre macro predefinite (non obbligatorie)

ISO / IEC 9899: 2011 §6.10.9.2 Macro ambiente:

  • __STDC_ISO_10646__ Una costante intera del formato yyyymmL (ad esempio, 199712L). Se questo simbolo è definito, ogni carattere nel set richiesto Unicode, se memorizzato in un oggetto di tipo wchar_t , ha lo stesso valore dell'identificatore breve di quel carattere. Il set Unicode richiesto è costituito da tutti i caratteri definiti da ISO / IEC 10646, insieme a tutti gli emendamenti e le rettifiche tecniche, a partire dall'anno e dal mese specificati. Se si utilizza un'altra codifica, la macro non deve essere definita e la codifica effettiva utilizzata è definita dall'implementazione.

  • __STDC_MB_MIGHT_NEQ_WC__ La costante intera 1, destinata a indicare che, nella codifica per wchar_t , un membro del set di caratteri di base non deve avere un valore di codice uguale al suo valore quando viene utilizzato come carattere solitario in una costante di carattere intero.

  • __STDC_UTF_16__ La costante intera 1, destinata a indicare che i valori di tipo char16_t sono codificati UTF-16. Se si utilizza un'altra codifica, la macro non deve essere definita e la codifica effettiva utilizzata è definita dall'implementazione.

  • __STDC_UTF_32__ La costante intera 1, destinata a indicare che i valori di tipo char32_t sono codificati UTF-32. Se si utilizza un'altra codifica, la macro non deve essere definita e la codifica effettiva utilizzata è definita dall'implementazione.

ISO / IEC 9899: 2011 §6.10.8.3 Macro delle funzioni condizionali

  • __STDC_ANALYZABLE__ La costante intera 1, destinata a indicare la conformità alle specifiche dell'allegato L (Analizzabilità).
  • __STDC_IEC_559__ La costante intera 1, destinata a indicare la conformità alle specifiche dell'allegato F (aritmetica in virgola mobile IEC 60559).
  • __STDC_IEC_559_COMPLEX__ La costante intera 1, destinata a indicare l'aderenza alle specifiche dell'allegato G (Aritmetica complessa compatibile con IEC 60559).
  • __STDC_LIB_EXT1__ La costante intera 201112L , destinata a indicare il supporto per le estensioni definite nell'allegato K (Interfacce di controllo dei limiti).
  • __STDC_NO_ATOMICS__ La costante intera 1, destinata a indicare che l'implementazione non supporta i tipi atomici (incluso il qualificatore di tipo _Atomic ) e l'intestazione <stdatomic.h> .
  • __STDC_NO_COMPLEX__ La costante intera 1, destinata a indicare che l'implementazione non supporta tipi complessi o l'intestazione <complex.h> .
  • __STDC_NO_THREADS__ La costante intera 1, destinata a indicare che l'implementazione non supporta l'intestazione <threads.h> .
  • __STDC_NO_VLA__ La costante intera 1, intesa a indicare che l'implementazione non supporta matrici di lunghezza variabile o tipi modificati in modo variabile.

L'intestazione include guardie

Praticamente ogni file di intestazione dovrebbe seguire l'idioma di protezione incluso :

my-header-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Ciò garantisce che quando si #include "my-header-file.h" in più punti, non si ottengano dichiarazioni duplicate di funzioni, variabili, ecc. Immaginate la seguente gerarchia di file:

header-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

header-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

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

int main() {
    // do something
}

Questo codice ha un problema serio: il contenuto dettagliato di MyStruct è definito due volte, cosa che non è consentita. Ciò comporterebbe un errore di compilazione che può essere difficile da rintracciare, poiché un file di intestazione ne include un altro. Se invece lo facevi con le guardie di intestazione:

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

main.c

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

int main() {
    // do something
}

Questo si espanderebbe quindi a:

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

Quando il compilatore raggiunge la seconda inclusione dell'header- HEADER_1_H , HEADER_1_H è già stato definito dall'inclusione precedente. Ergo, si riduce a quanto segue:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

E quindi non c'è errore di compilazione.

Nota: esistono diverse convenzioni per nominare le protezioni dell'intestazione. Ad alcune persone piace HEADER_2_H_ , alcuni includono il nome del progetto come MY_PROJECT_HEADER_2_H . L'importante è assicurarsi che la convenzione che segui faccia in modo che ogni file del tuo progetto abbia una protezione di intestazione unica.


Se i dettagli della struttura non sono stati inclusi nell'intestazione, il tipo dichiarato sarebbe incompleto o di tipo opaco . Tali tipi possono essere utili, nascondendo i dettagli di implementazione dagli utenti delle funzioni. Per molti scopi, il tipo FILE nella libreria C standard può essere considerato un tipo opaco (sebbene di solito non sia opaco in modo che le implementazioni macro delle funzioni di I / O standard possano fare uso degli interni della struttura). In tal caso, l' header-1.h potrebbe contenere:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Si noti che la struttura deve avere un nome di tag (qui MyStruct - che si trova nello spazio dei nomi dei tag, separato dallo spazio dei nomi identificativo ordinario del nome typedef MyStruct ) e che { … } è omesso. Questo dice "c'è un tipo di struttura struct MyStruct e c'è un alias per esso MyStruct ".

Nel file di implementazione, è possibile definire i dettagli della struttura per rendere il tipo completo:

struct MyStruct {
    …
};

Se si utilizza C11, è possibile ripetere la typedef struct MyStruct MyStruct; dichiarazione senza causare un errore di compilazione, ma le versioni precedenti di C si lamentavano. Di conseguenza, è ancora meglio utilizzare l'idioma di include include, anche se in questo esempio, sarebbe facoltativo se il codice fosse stato compilato solo con compilatori che supportavano C11.


Molti compilatori supportano la direttiva #pragma once , che ha gli stessi risultati:

my-header-file.h

#pragma once

// Code for header file

Tuttavia, #pragma once non fa parte dello standard C, quindi il codice è meno portabile se lo si utilizza.


Alcune intestazioni non usano l'idioma di guardia incluso. Un esempio specifico è l'intestazione <assert.h> standard. Può essere incluso più volte in una singola unità di traduzione e l'effetto di tale operazione dipende dal fatto che la macro NDEBUG sia definita ogni volta che viene inclusa l'intestazione. Occasionalmente potresti avere un requisito analogo; questi casi saranno pochi e lontani tra loro. Normalmente, le intestazioni devono essere protette dall'idioma di protezione incluso.

ESEGUI l'implementazione

Possiamo anche usare macro per rendere il codice più facile da leggere e scrivere. Ad esempio, possiamo implementare macro per implementare il costrutto foreach in C per alcune strutture dati come elenchi, code, ecc.

Ecco un piccolo esempio.

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

È possibile creare un'interfaccia standard per tali strutture di dati e scrivere un'implementazione generica di FOREACH come:

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

Per utilizzare questa implementazione generica, è sufficiente implementare queste funzioni per la struttura dei dati.

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

__cplusplus per l'uso di C esterni in codice C ++ compilato con C ++ - nome mangling

Ci sono momenti in cui un file di inclusione deve generare un output diverso dal preprocessore a seconda che il compilatore sia un compilatore C o un compilatore C ++ a causa delle differenze linguistiche.

Ad esempio una funzione o un altro esterno è definito in un file sorgente C ma è usato in un file sorgente C ++. Poiché C ++ utilizza il nome mangling (o name decoration) per generare nomi di funzioni univoci basati su tipi di argomenti di funzione, una dichiarazione di funzione C utilizzata in un file di origine C ++ causerà errori di collegamento. Il compilatore C ++ modificherà il nome esterno specificato per l'output del compilatore utilizzando le regole di mangling del nome per C ++. Il risultato sono errori di collegamento dovuti a elementi esterni non trovati quando l'output del compilatore C ++ è collegato all'output del compilatore C.

Dal momento che i compilatori C non nominano il mangling ma i compilatori C ++ fanno per tutte le etichette esterne (nomi di funzioni o nomi di variabili) generati dal compilatore C ++, è stata introdotta una macro preprocessore predefinita, __cplusplus , per consentire il rilevamento del compilatore.

Per aggirare questo problema di output del compilatore incompatibile per nomi esterni tra C e C ++, la macro __cplusplus è definita nel preprocessore C ++ e non è definita nel preprocessore C. Questo nome di macro può essere utilizzato con la direttiva #ifdef preprocessore condizionale o con l'operatore di defined() con #if defined() per indicare se un codice sorgente o un file di inclusione viene compilato come C ++ o C.

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

O potresti usare

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

Per specificare il nome corretto della funzione di una funzione da un file sorgente C compilato con il compilatore C che viene utilizzato in un file sorgente C ++, è possibile verificare la costante definita __cplusplus per causare la extern "C" { /* ... */ }; da utilizzare per dichiarare esterni C quando il file di intestazione è incluso in un file di origine C ++. Tuttavia, quando compilato con un compilatore C, l' extern "C" { */ ... */ }; non è usato Questa compilazione condizionale è necessaria perché extern "C" { /* ... */ }; è valido in C ++ ma non 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

Macro simili a funzioni

Le macro di tipo funzione sono simili alle funzioni inline , utili in alcuni casi, ad esempio il registro di debug temporaneo:

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

Qui in entrambi i casi (con DEBUG o meno) la chiamata si comporta allo stesso modo di una funzione con tipo di ritorno void . Ciò garantisce che i condizionali if/else siano interpretati come previsto.

Nel caso DEBUG questo è implementato attraverso un costrutto do { ... } while(0) . Nell'altro caso, (void)0 è un'affermazione senza effetti collaterali che viene semplicemente ignorata.

Un'alternativa per quest'ultimo sarebbe

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

tale che sia in tutti i casi sintatticamente equivalente al primo.

Se si utilizza GCC, è anche possibile implementare una macro simile alla funzione che restituisce il risultato utilizzando un'estensione GNU non standard - espressioni di istruzioni . Per esempio:

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

Argomenti Variadici macro

C99

Macro con argomenti variadici:

Supponiamo che tu voglia creare una macro di stampa per il debug del tuo codice, prendiamo questa macro come esempio:

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

Alcuni esempi di utilizzo:

La funzione somefunc() restituisce -1 se fallisce e 0 se ha successo, e viene chiamata da molti posti diversi all'interno del codice:

int retVal = somefunc();

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

/* some other code */

 retVal = somefunc();

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

Cosa succede se l'implementazione di somefunc() cambia e restituisce ora valori diversi che corrispondono a diversi possibili tipi di errore? Si desidera comunque utilizzare la macro di debug e stampare il valore dell'errore.

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

Per risolvere questo problema è stata introdotta la macro __VA_ARGS__ . Questa macro consente più parametri X-macro:

Esempio:

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

Uso:

int retVal = somefunc();

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

Questa macro ti consente di passare più parametri e stamparli, ma ora ti impedisce di inviare alcun parametro.

debug_print("Hey");

Ciò solleverebbe un errore di sintassi poiché la macro si aspetta almeno un altro argomento e il pre-processore non ignorerebbe la mancanza di virgola nella macro debug_print() . Anche debug_print("Hey",); solleverebbe un errore di sintassi poiché non si può mantenere l'argomento passato alla macro vuota.

Per risolvere questo problema, è stata introdotta la macro ##__VA_ARGS__ , questa macro indica che se non esistono argomenti variabili, la virgola viene cancellata dal pre-processore dal codice.

Esempio:

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

Uso:

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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow