C Language
Preprocesor i makra
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 formatuyyyymmL
(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 razie0
. -
__STDC__
Jeśli1
, 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 postaciyyyymmL
(na przykład 199712L). Jeśli ten symbol jest zdefiniowany, to każdy znak w wymaganym zestawie Unicode, gdy jest przechowywany w obiekcie typuwchar_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 kodowaniuwchar_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 typuchar16_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 typuchar32_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ła201112L
, 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
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());