Szukaj…


Wprowadzenie

Wszystkie polecenia preprocesora zaczynają się od symbolu skrótu (funta) # . Makro AC to tylko polecenie preprocesora, które jest definiowane za pomocą dyrektywy #define preprocessor. Podczas etapu przetwarzania wstępnego preprocesor C (część kompilatora C) po prostu zastępuje treść makra wszędzie tam, gdzie pojawia się jego nazwa.

Uwagi

Kiedy kompilator napotka makro w kodzie, dokonuje prostej zamiany łańcucha, nie są wykonywane żadne dodatkowe operacje. Z tego powodu zmiany dokonywane przez preprocesor nie uwzględniają zakresu programów w C - na przykład definicja makra nie ogranicza się do bycia w bloku, więc nie ma na nią wpływu '}' który kończy instrukcję bloku.

Preprocesor z założenia nie jest ukończony - istnieje kilka rodzajów obliczeń, których sam preprocesor nie może wykonać.

Zwykle kompilatory mają flagę wiersza poleceń (lub ustawienie konfiguracji), która pozwala nam zatrzymać kompilację po fazie wstępnego przetwarzania i sprawdzić wynik. Na platformach POSIX ta flaga to -E . Tak więc uruchomienie gcc z tą flagą wypisuje rozwinięte źródło na standardowe wyjście:

$ gcc -E cprog.c

Często preprocesor jest implementowany jako osobny program, który jest wywoływany przez kompilator, popularna nazwa tego programu to cpp . Wiele preprocesorów emituje informacje pomocnicze, takie jak informacje o numerach linii - które są wykorzystywane przez kolejne fazy kompilacji do generowania informacji debugowania. W przypadku, gdy preprocesor jest oparty na gcc, opcja -P pomija takie informacje.

$ cpp -P cprog.c

Włączanie warunkowe i modyfikacja sygnatury funkcji warunkowych

Aby warunkowo dołączyć blok kodu, preprocesor ma kilka dyrektyw (np. #if , #ifdef , #else , #endif itp.).

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

Do warunku #if można zastosować normalne operatory relacyjne C.

#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

Dyrektywy #if if zachowują się podobnie do instrukcji C if , powinny zawierać tylko stałe wyrażenia stałe i nie mogą rzutować. Obsługuje jeden dodatkowy operator jednoargumentowy, defined( identifier ) , który zwraca 1 jeśli identyfikator jest zdefiniowany, a 0 przeciwnym razie.

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

Modyfikacja podpisu funkcji warunkowej

W większości przypadków oczekuje się, że wersja aplikacji będzie miała jak najmniejszy narzut. Jednak podczas testowania kompilacji tymczasowej pomocne mogą być dodatkowe dzienniki i informacje o wykrytych problemach.

Załóżmy na przykład, że istnieje funkcja SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) która podczas wykonywania kompilacji testowej jest pożądana, aby wygenerować dziennik jej użycia. Jednak ta funkcja jest używana w wielu miejscach i pożądane jest, aby podczas generowania dziennika część informacji miała wiedzieć, skąd funkcja jest wywoływana.

Używając kompilacji warunkowej, możesz w pliku dołączającym zadeklarować tę funkcję w następujący sposób. Zastępuje to standardową wersję funkcji wersją debugowania funkcji. Preprocesor służy do zastępowania wywołań funkcji SerOpPluAllRead() wywołaniami funkcji SerOpPluAllRead_Debug() dwoma dodatkowymi argumentami, nazwą pliku i numerem wiersza, w którym funkcja jest używana.

Kompilacja warunkowa służy do wyboru, czy zastąpić funkcję standardową wersją debugującą, czy nie.

#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

Pozwala to zastąpić standardową wersję funkcji SerOpPluAllRead() wersją, która poda nazwę pliku i numer linii w pliku, w którym funkcja jest wywoływana.

Jest jedna ważna uwaga: każdy plik używający tej funkcji musi zawierać plik nagłówka, w którym zastosowano to podejście, aby preprocesor mógł zmodyfikować funkcję. W przeciwnym razie zobaczysz błąd linkera.

Definicja funkcji wyglądałaby podobnie do poniższej. Źródło to żąda, aby preprocesor zmienił nazwę funkcji SerOpPluAllRead() na SerOpPluAllRead_Debug() i zmodyfikował listę argumentów, aby zawierały dwa dodatkowe argumenty, wskaźnik do nazwy pliku, w którym funkcja została wywołana, i numer linii w pliku, w którym używana jest funkcja.

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

Dołączenie pliku źródłowego

Najczęstsze zastosowania dyrektyw wstępnego przetwarzania #include są następujące:

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

#include zastępuje instrukcję zawartością wskazanego pliku. Nawiasy kątowe (<>) odnoszą się do plików nagłówkowych zainstalowanych w systemie, natomiast znaki cudzysłowu („”) dotyczą plików dostarczanych przez użytkownika.

Same makra mogą raz rozwinąć inne makra, co ilustruje ten przykład:

#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

Wymiana makr

Najprostszą formą zamiany makr jest zdefiniowanie manifest constant , jak w

#define ARRSIZE 100
int array[ARRSIZE];

Definiuje to funkcjonalne makro, które mnoży zmienną przez 10 i przechowuje nową wartość:

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

Zastąpienie następuje przed jakąkolwiek inną interpretacją tekstu programu. W pierwszym wywołaniu TIMES10 nazwa A z definicji zostaje zastąpiona przez b a następnie rozwinięty tekst jest wstawiany zamiast wywołania. Zauważ, że ta definicja TIMES10 nie jest równoważna

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

ponieważ może to dwukrotnie ocenić zastąpienie A , co może mieć niepożądane skutki uboczne.

Poniżej zdefiniowano makro funkcyjne, którego wartość stanowi maksimum jego argumentów. Ma to tę zaletę, że pracuje dla dowolnych kompatybilnych typów argumentów i generuje kod in-line bez narzutu wywołania funkcji. Ma to wady polegające na ponownej ocenie jednego lub drugiego argumentu (w tym skutków ubocznych) i generowaniu więcej kodu niż funkcji, jeśli zostanie wywołany kilka razy.

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

Z tego powodu w kodzie produkcyjnym zwykle unika się makr, które wielokrotnie oceniają swoje argumenty. Od wersji C11 istnieje funkcja _Generic która pozwala uniknąć wielu wywołań.

Obfite nawiasy w rozwinięciach makr (prawa strona definicji) zapewniają, że argumenty i wynikowe wyrażenie są odpowiednio powiązane i dobrze pasują do kontekstu, w którym makro jest wywoływane.

Dyrektywa o błędach

Jeśli preprocesor napotka dyrektywę #error , kompilacja zostanie zatrzymana, a dołączony komunikat diagnostyczny zostanie wydrukowany.

#define DEBUG

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

int main(void) {
    return 0;
}

Możliwe wyjście:

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

# jeśli 0, aby zablokować sekcje kodu

Jeśli istnieją sekcje kodu, które rozważasz usunąć lub chcesz tymczasowo wyłączyć, możesz je skomentować za pomocą komentarza blokowego.

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

Jeśli jednak kod źródłowy, który otoczyłeś komentarzem bloku, ma w źródle komentarze w stylu bloku, końcowe * / istniejących komentarzy bloku może spowodować, że nowy komentarz bloku będzie nieprawidłowy i spowoduje problemy z kompilacją.

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

W poprzednim przykładzie kompilator widzi ostatnie dwa wiersze funkcji i ostatnie „* /”, więc kompiluje się z błędami. Bezpieczniejszą metodą jest użycie dyrektywy #if 0 wokół kodu, który chcesz zablokować.

#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

Zaletą tego jest to, że jeśli chcesz wrócić i znaleźć kod, o wiele łatwiej jest wyszukać „#if 0” niż przeszukać wszystkie komentarze.

Inną bardzo ważną zaletą jest to, że można zagnieżdżać komentowanie kodu za pomocą #if 0 . Nie można tego zrobić z komentarzami.

Alternatywą dla użycia #if 0 jest użycie nazwy, która nie będzie # #defined ale bardziej opisowa, dlaczego kod jest blokowany. Na przykład, jeśli istnieje funkcja, która wydaje się być bezużytecznym martwym kodem, możesz użyć #if defined(POSSIBLE_DEAD_CODE) lub #if defined(FUTURE_CODE_REL_020201) dla kodu potrzebnego, gdy inna funkcjonalność zostanie wprowadzona lub coś podobnego. Następnie, gdy wracasz do usunięcia lub włączenia tego źródła, te sekcje źródła są łatwe do znalezienia.

Wklejanie żetonów

Wklejanie tokenów pozwala skleić dwa argumenty makr. Na przykład front##back daje frontback . Znanym przykładem jest nagłówek Win32 <TCHAR.H> . W standardowym C można napisać L"string" aby zadeklarować szeroki ciąg znaków. Jednak Windows API pozwala na konwersję między szerokimi ciągami znaków a wąskimi ciągami znaków poprzez #define zdefiniowanie UNICODE . Aby zaimplementować literały łańcuchowe, TCHAR.H używa tego

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

Ilekroć użytkownik pisze TEXT("hello, world") , a UNICODE jest zdefiniowane, preprocesor C łączy L i argument makra. L połączone z "hello, world" daje L"hello, world" .

Predefiniowane makra

Wstępnie zdefiniowane makro to makro, które jest już zrozumiałe dla procesora C bez konieczności definiowania go przez program. Przykłady zawierają

Obowiązkowe wstępnie zdefiniowane makra

  • __FILE__ , który podaje nazwę bieżącego pliku źródłowego (literał ciągu),
  • __LINE__ dla bieżącego numeru linii (stała całkowita),
  • __DATE__ dla daty kompilacji (dosłowny ciąg znaków),
  • __TIME__ dla czasu kompilacji (dosłowny ciąg znaków).

Istnieje również powiązany predefiniowany identyfikator __func__ (ISO / IEC 9899: 2011 §6.4.2.2), który nie jest makrem:

Identyfikator __func__ jest domyślnie deklarowany przez tłumacza tak, jakby deklaracja: bezpośrednio po __func__ otwierającym każdej definicji funkcji:

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

pojawił się, gdzie nazwa-funkcji jest nazwą funkcji zamykającej leksykalnie.

__FILE__ , __LINE__ i __func__ są szczególnie przydatne do celów debugowania. Na przykład:

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

Kompilatory __func__ niż C99 mogą, ale nie __func__ obsługiwać __func__ lub mogą mieć makro działające tak samo o różnych nazwach. Na przykład gcc użył __FUNCTION__ w trybie C89.

Poniższe makra pozwalają zapytać o szczegóły dotyczące implementacji:

  • __STDC_VERSION__ wersja standardu C. Jest to stała liczba całkowita używająca formatu yyyymmL (wartość 201112L dla C11, wartość 199901L dla C99; nie została zdefiniowana dla C89 / C90)
  • __STDC_HOSTED__ 1 jeśli jest to hostowana implementacja, w przeciwnym razie 0 .
  • __STDC__ Jeśli 1 , implementacja jest zgodna ze standardem C.

Inne wstępnie zdefiniowane makra (nieobowiązkowe)

ISO / IEC 9899: 2011 §6.10.9.2 Makra środowiska:

  • __STDC_ISO_10646__ Stała całkowita w postaci yyyymmL (na przykład 199712L). Jeśli ten symbol jest zdefiniowany, to każdy znak w wymaganym zestawie Unicode, gdy jest przechowywany w obiekcie typu wchar_t , ma taką samą wartość jak krótki identyfikator tego znaku. Wymagany zestaw Unicode składa się ze wszystkich znaków zdefiniowanych przez ISO / IEC 10646, wraz ze wszystkimi poprawkami i sprostowaniami technicznymi, na określony rok i miesiąc. Jeśli zostanie użyte inne kodowanie, makro nie zostanie zdefiniowane, a rzeczywiste zastosowane kodowanie zostanie zdefiniowane w ramach implementacji.

  • __STDC_MB_MIGHT_NEQ_WC__ Stała liczb całkowitych 1, przeznaczona do wskazania, że w kodowaniu wchar_t członek podstawowego zestawu znaków nie musi mieć wartości kodu równej jej wartości, gdy jest używany jako samotny znak w stałej liczb całkowitych.

  • __STDC_UTF_16__ Stała liczb całkowitych 1, przeznaczona do wskazania, że wartości typu char16_t są zakodowane w UTF-16. Jeśli zostanie użyte inne kodowanie, makro nie zostanie zdefiniowane, a rzeczywiste zastosowane kodowanie zostanie zdefiniowane w ramach implementacji.

  • __STDC_UTF_32__ Stała liczb całkowitych 1, przeznaczona do wskazania, że wartości typu char32_t są zakodowane w UTF-32. Jeśli zostanie użyte inne kodowanie, makro nie zostanie zdefiniowane, a rzeczywiste zastosowane kodowanie zostanie zdefiniowane w ramach implementacji.

ISO / IEC 9899: 2011 §6.10.8.3 Makra funkcji warunkowych

  • __STDC_ANALYZABLE__ Stała całkowita 1, mająca wskazywać zgodność ze specyfikacjami w załączniku L (Analizowalność).
  • __STDC_IEC_559__ Stała liczb całkowitych 1, przeznaczona do wskazania zgodności ze specyfikacjami w załączniku F (zmiennoprzecinkowa arytmetyka IEC 60559).
  • __STDC_IEC_559_COMPLEX__ Stała liczb całkowitych 1, przeznaczona do wskazania zgodności ze specyfikacjami w załączniku G (arytmetyka zgodna z IEC 60559).
  • __STDC_LIB_EXT1__ Stała 201112L , przeznaczona do wskazywania wsparcia dla rozszerzeń zdefiniowanych w załączniku K (interfejsy sprawdzania granic).
  • __STDC_NO_ATOMICS__ Stała liczb całkowitych 1, mająca na celu wskazanie, że implementacja nie obsługuje typów atomowych (w tym kwalifikatora typu _Atomic ) i nagłówka <stdatomic.h> .
  • __STDC_NO_COMPLEX__ Stała liczb całkowitych 1, przeznaczona do wskazania, że implementacja nie obsługuje typów złożonych ani nagłówka <complex.h> .
  • __STDC_NO_THREADS__ Stała liczb całkowitych 1, przeznaczona do wskazania, że implementacja nie obsługuje nagłówka <threads.h> .
  • __STDC_NO_VLA__ Stała liczb całkowitych 1, mająca na celu wskazanie, że implementacja nie obsługuje tablic o zmiennej długości ani zmiennych typów.

Nagłówek obejmuje strażników

Prawie każdy plik nagłówkowy powinien być zgodny z idiomem strażnika :

my-header-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Zapewnia to, że #include "my-header-file.h" w wielu miejscach nie otrzyma duplikatów deklaracji funkcji, zmiennych itp. Wyobraź sobie następującą hierarchię plików:

nagłówek 1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

nagłówek 2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

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

int main() {
    // do something
}

Ten kod ma poważny problem: szczegółowa zawartość MyStruct jest definiowana dwukrotnie, co jest niedozwolone. Spowodowałoby to błąd kompilacji, który może być trudny do wyśledzenia, ponieważ jeden plik nagłówka zawiera inny. Jeśli zamiast tego zrobiłeś to z osłonami nagłówków:

nagłówek 1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

nagłówek 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
}

To rozwinie się do:

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

Gdy kompilator osiągnie drugie włączenie nagłówka HEADER_1_H , HEADER_1_H został już zdefiniowany przez poprzednie włączenie. Ergo sprowadza się do następujących kwestii:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

I dlatego nie ma błędu kompilacji.

Uwaga: Istnieje wiele różnych konwencji nazewnictwa osłon nagłówka. Niektóre osoby lubią nazywać to HEADER_2_H_ , niektóre zawierają nazwę projektu jak MY_PROJECT_HEADER_2_H . Ważne jest, aby upewnić się, że przestrzegana konwencja sprawia, że każdy plik w projekcie ma unikalny nagłówek.


Jeśli szczegóły struktury nie zostałyby zawarte w nagłówku, zadeklarowany typ byłby niekompletny lub nieprzezroczysty . Takie typy mogą być przydatne, ukrywając szczegóły implementacji przed użytkownikami funkcji. Dla wielu celów typ FILE w standardowej bibliotece C można uznać za typ nieprzezroczysty (chociaż zwykle nie jest nieprzezroczysty, tak więc implementacje makr standardowych funkcji We / Wy mogą wykorzystywać elementy wewnętrzne struktury). W takim przypadku header-1.h może zawierać:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Zauważ, że struktura musi mieć nazwę znacznika (tutaj MyStruct - znajduje się w przestrzeni nazw znaczników, odrębnej od zwykłej przestrzeni nazw identyfikatorów typedef nazwa MyStruct ) i że { … } jest pominięty. Mówi to: „istnieje struktura typu struct MyStruct i istnieje alias MyStruct ”.

W pliku implementacji można zdefiniować szczegóły struktury, aby uzupełnić typ:

struct MyStruct {
    …
};

Jeśli używasz C11, możesz powtórzyć typedef struct MyStruct MyStruct; deklaracja bez powodowania błędu kompilacji, ale wcześniejsze wersje języka C narzekałyby. W związku z tym nadal najlepiej jest używać idiomu ochronnego włączającego, chociaż w tym przykładzie byłoby opcjonalne, gdyby kod był kiedykolwiek kompilowany tylko z kompilatorami obsługującymi C11.


Wiele kompilatorów obsługuje dyrektywę #pragma once , która daje takie same wyniki:

my-header-file.h

#pragma once

// Code for header file

Jednak #pragma once nie jest częścią standardu C, więc kod jest mniej przenośny, jeśli go używasz.


Kilka nagłówków nie używa idiomu osłony. Jednym konkretnym przykładem jest standardowy nagłówek <assert.h> . Może być zawarty wielokrotnie w jednej jednostce tłumaczeniowej, a efekt tego zależy od tego, czy makro NDEBUG jest definiowane za każdym razem, gdy dołączany jest nagłówek. Czasami możesz mieć analogiczne wymagania; takich przypadków będzie niewiele. Zwykle nagłówki powinny być chronione przez idiom dołączenia strażnika.

Wdrożenie FOREACH

Możemy również użyć makr, aby ułatwić odczyt i zapis kodu. Na przykład możemy zaimplementować makra do implementacji konstrukcji foreach w C dla niektórych struktur danych, takich jak listy jedno- i podwójnie połączone, kolejki itp.

Oto mały przykład.

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

Możesz stworzyć standardowy interfejs dla takich struktur danych i napisać ogólną implementację FOREACH jako:

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

Aby użyć tej ogólnej implementacji, wystarczy zaimplementować te funkcje w strukturze danych.

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

__cplusplus za używanie zewnętrznych elementów C w kodzie C ++ skompilowanym z C ++ - mangling nazw

Czasami plik dołączania musi generować różne dane wyjściowe z preprocesora w zależności od tego, czy kompilator jest kompilatorem C, czy kompilatorem C ++ ze względu na różnice językowe.

Na przykład funkcja lub inny element zewnętrzny jest zdefiniowany w pliku źródłowym C, ale jest używany w pliku źródłowym C ++. Ponieważ C ++ używa modyfikacji nazw (lub dekorowania nazw) w celu generowania unikalnych nazw funkcji na podstawie typów argumentów funkcji, deklaracja funkcji C zastosowana w pliku źródłowym C ++ spowoduje błędy łącza. Kompilator C ++ zmodyfikuje podaną nazwę zewnętrzną dla danych wyjściowych kompilatora, używając reguł zmiany nazwy dla C ++. Rezultatem są błędy łącza z powodu zewnętrznych elementów, których nie znaleziono, gdy dane wyjściowe kompilatora C ++ są połączone z danymi wyjściowymi kompilatora C.

Ponieważ kompilatory C nie zmieniają nazw, ale kompilatory C ++ robią to dla wszystkich zewnętrznych etykiet (nazw funkcji lub nazw zmiennych) generowanych przez kompilator C ++, wprowadzono predefiniowane makro preprocesora __cplusplus , aby umożliwić wykrywanie kompilatora.

Aby obejść ten problem niekompatybilnego wyjścia kompilatora dla nazw zewnętrznych między C i C ++, makro __cplusplus jest zdefiniowane w Preprocesorze C ++ i nie jest zdefiniowane w Preprocesorze C. Tej nazwy makra można użyć z warunkowym preprocesorem #ifdef dyrektywy lub #if z operatorem defined() , aby stwierdzić, czy kod źródłowy czy plik dołączany jest kompilowany jako C ++ czy C.

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

Lub możesz użyć

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

Aby określić poprawną nazwę funkcji funkcji z pliku źródłowego C skompilowanego za pomocą kompilatora C używanego w pliku źródłowym C ++, można sprawdzić stałą stałą __cplusplus , aby spowodować wystąpienie extern "C" { /* ... */ }; do użycia w celu zadeklarowania zewnętrznych elementów C, gdy plik nagłówkowy znajduje się w pliku źródłowym C ++. Jednak po skompilowaniu za pomocą kompilatora C, extern "C" { */ ... */ }; nie jest używany. Ta kompilacja warunkowa jest potrzebna, ponieważ extern "C" { /* ... */ }; jest poprawny w C ++, ale nie w 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

Makra funkcjonalne

Makra podobne do funkcji są podobne do funkcji inline , są one przydatne w niektórych przypadkach, takich jak tymczasowy dziennik debugowania:

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

Tutaj w obu przypadkach (z DEBUG lub bez) wywołanie zachowuje się tak samo, jak funkcja z typem void return. Zapewnia to, że warunki warunkowe if/else są interpretowane zgodnie z oczekiwaniami.

W przypadku DEBUG jest to realizowane przez konstrukcję do { ... } while(0) . W innym przypadku (void)0 jest instrukcją bez efektu ubocznego, która jest po prostu ignorowana.

Alternatywą dla tego drugiego byłoby

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

tak, że we wszystkich przypadkach jest on syntaktycznie równoważny z pierwszym.

Jeśli używasz GCC, możesz także zaimplementować funkcjonalne makro, które zwraca wynik za pomocą niestandardowego rozszerzenia GNU - wyrażenia instrukcji . Na przykład:

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

Makro argumentów Variadic

C99

Makra z różnymi argumentami:

Powiedzmy, że chcesz utworzyć makro-drukowania do debugowania kodu, weźmy to makro jako przykład:

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

Niektóre przykłady użycia:

Funkcja somefunc() zwraca -1, jeśli się nie powiedzie, i 0, jeśli się powiedzie, i jest wywoływana z wielu różnych miejsc w kodzie:

int retVal = somefunc();

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

/* some other code */

 retVal = somefunc();

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

Co się stanie, jeśli implementacja somefunc() zmieni się, a teraz zwraca różne wartości pasujące do różnych możliwych typów błędów? Nadal chcesz użyć makra debugowania i wydrukować wartość błędu.

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

Aby rozwiązać ten problem, wprowadzono makro __VA_ARGS__ . To makro pozwala na wiele parametrów X-makro:

Przykład:

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

Stosowanie:

int retVal = somefunc();

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

To makro pozwala przekazać wiele parametrów i wydrukować je, ale teraz w ogóle zabrania wysyłania jakichkolwiek parametrów.

debug_print("Hey");

Spowodowałoby to pewien błąd składniowy, ponieważ makro oczekuje co najmniej jeszcze jednego argumentu, a procesor wstępny nie zignoruje braku przecinka w debug_print() . Również debug_print("Hey",); spowodowałoby błąd składniowy, ponieważ nie można pozostawić argumentu przekazanego do makra pustego.

Aby rozwiązać ten problem, wprowadzono makro ##__VA_ARGS__ makro to stwierdza, że jeśli nie istnieją argumenty zmienne, przecinek jest usuwany z kodu przez procesor wstępny.

Przykład:

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

Stosowanie:

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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow