Makro to nazwa, którą preprocesor zastępuje jakimś tekstem, zanim uruchomi się kompilator. To cały mechanizm, a każda reguła na tej stronie z niego wynika: makra nie mają typów, nie respektują zasięgu, nie obliczają argumentów i nie wiedzą, czym jest wywołanie funkcji. Kopiują tekst.
Używane ostrożnie są niezastąpione: przełączniki przenośności, makra asercji przechwytujące numer linii, stałe czasu kompilacji działające w rozmiarach tablic. Używane niedbale produkują błędy, które wyglądają na niemożliwe, dopóki nie rozwiniesz pliku.
Makra obiektowe
Najprostsza forma definiuje nazwę, która oznacza wartość:
Zwyczajowo takie nazwy zapisuje się jako UPPER_SNAKE_CASE, żeby czytelnik od razu widział, że symbol to makro, a nie zmienna. Ta konwencja ma tu większe znaczenie niż zwykle, bo makra ignorują zasięg: #define wewnątrz funkcji nadal obowiązuje w reszcie pliku i bez oporów przepisze zmienną o tej samej nazwie w innej funkcji.
#undef NAME usuwa definicję, więc nazwę można później zdefiniować na nowo bez ostrzeżenia o redefinicji.
Makra funkcyjne
Umieść listę parametrów bezpośrednio po nazwie, bez spacji przed nawiasem otwierającym, a makro będzie przyjmować argumenty:
Reguła spacji jest prawdziwa: #define SQUARE (x) ((x)*(x)) definiuje makro obiektowe o nazwie SQUARE, którego tekst zastępczy zaczyna się od (x). Błąd kompilatora, który potem wystąpi, nie wspomni o spacji.
Zasady nawiasów
Są dwie i obie istnieją dlatego, że ciało jest wklejane w wyrażenie, którego nie widzisz, pisząc makro.
Zasada 1: każdy parametr w nawiasach. Bez tego argument, który sam jest wyrażeniem, zostaje rozerwany przez priorytety operatorów:
#define SQUARE_BAD(x) x * x
SQUARE_BAD(2 + 3) /* staje się 2 + 3 * 2 + 3 == 11, a nie 25 */
Zasada 2: całe ciało w nawiasach. Bez tego otaczające wyrażenie rozrywa wynik:
#define DOUBLE_BAD(x) (x) + (x)
10 / DOUBLE_BAD(5) /* staje się 10 / (5) + (5) == 7, a nie 1 */
Uruchom obie porażki obok ich poprawionych wersji:
Obie złe wersje kompilują się bez jednego ostrzeżenia i dają złe liczby. Nawyk do wyrobienia jest mechaniczny: każdy parametr w nawiasy, potem wynik w nawiasy. ((a) > (b) ? (a) : (b)) wygląda na zaszumione, a ten szum jest celowy.
Pułapka wielokrotnego obliczania
Nawiasy nie uratują cię przed drugim zagrożeniem. Makro wkleja tekst argumentu w każdym miejscu, w którym pojawia się parametr, więc argument z efektem ubocznym wykonuje się więcej niż raz:
Makro rozwija się do ((i++) > (j) ? (i++) : (j)). Porównanie zwiększa i do 6 i porównuje 5 z 3; gałąź prawdy znowu zwiększa i do 7 i daje 6. Zatem m to 6, a i to 7, a żadna z tych wartości nie jest tym, co powinno dać "większe z i i j". Funkcja max(i++, j) działałaby poprawnie, bo argument jest obliczany raz przed wywołaniem.
Ta sama pułapka łapie wszystko, co ma efekt uboczny: SQUARE(rand()) wywołuje rand() dwa razy i mnoży dwie różne liczby. CHECK(read_byte()) pochłania dwa bajty.
Dwie obrony, w kolejności preferencji:
- Użyj prawdziwej funkcji. Jeśli potrzebujesz jej dla kilku typów, napisz po jednej na typ albo użyj funkcji
static inlinew nagłówku. - Jeśli to musi być makro, udokumentuj wyraźnie, że argumenty są obliczane więcej niż raz, i nie umieszczaj efektów ubocznych w miejscach wywołania.
Makra wieloliniowe i do-while(0)
Ciało makra może obejmować kilka linii, jeśli każda linia poza ostatnią kończy się ukośnikiem wstecznym. Naiwna wersja wygląda dobrze:
#define LOG_TWICE(msg) \
printf("%s\n", msg); \
printf("%s\n", msg)
A potem psuje się po cichu dokładnie tam, gdzie ma to znaczenie:
if (verbose)
LOG_TWICE("hello");
else
printf("cicho\n");
Po podstawieniu if obejmuje tylko pierwszy printf, drugi wykonuje się bezwarunkowo, a else nie ma już pasującego if. To błąd kompilacji, którego komunikat nie wskazuje niczego przydatnego. Objęcie ciała zwykłymi klamrami nie pomaga: końcowy ; po LOG_TWICE("hello") staje się pustą instrukcją, która kończy if, i else znowu się psuje.
Działa idiom do { ... } while (0):
do { ... } while (0) to pojedyncza instrukcja, która wykonuje swoje ciało dokładnie raz i wymaga średnika po sobie, więc miejsce wywołania wygląda jak zwykłe wywołanie funkcji i zachowuje się tak w każdym kontekście przepływu sterowania. Uważaj na ukośniki wsteczne: zabłąkana spacja po ukośniku kończy kontynuację i powoduje niezrozumiały błąd.
Makra predefiniowane i wzorce asercji
Preprocesor sam definiuje kilka makr i to z ich powodu niektórych rzeczy po prostu nie da się zrobić funkcją:
__FILE__ i __LINE__ rozwijają się tam, gdzie zostały napisane, więc umieszczenie ich w makrze przechwytuje pozycję wywołującego. Funkcja by tego nie potrafiła: wewnątrz funkcji zawsze zgłaszałyby plik i linię samej funkcji logującej. Dokładnie tak standardowe makro assert zgłasza położenie niespełnionego wyrażenia.
Inne przydatne nazwy predefiniowane: __DATE__, __TIME__ i __STDC_VERSION__ (na przykład 201710L dla C17).
Dwa operatory, które warto znać
Wewnątrz ciała makra # zamienia parametr w literał łańcuchowy ("stringizing"), a ## skleja tokeny ("token pasting").
SHOW(width * height) staje się printf("width * height" " = %d\n", (width * height));: kompilator łączy sąsiadujące literały łańcuchowe, więc jedno wywołanie wypisuje zarówno tekst wyrażenia, jak i jego wartość. To sztuczka debugowania warta zapamiętania.
## jest rzadszy i pojawia się głównie w makrach generujących kod: #define MAKE_VAR(n) int var_##n zamienia MAKE_VAR(3) w int var_3. Używaj go oszczędnie; identyfikatorów zbudowanych przez preprocesor nie da się wyszukać po nazwie, co utrudnia poruszanie się po kodzie.
Makra, funkcje i const
Używaj najsłabszego narzędzia, które wykona zadanie:
- Zmienna
const:const double PI = 3.14159;ma typ, podlega zasięgowi, widać ją w debuggerze i nie może jej przedefiniować niezwiązany nagłówek. Wybieraj ją dla wartości używanych w czasie działania. enum:enum { MAX_USERS = 100 };daje nazwaną stałą całkowitą, która działa tam, gdzie C wymaga stałej czasu kompilacji, i ma typ oraz zasięg. Dobra na limity całkowite.- Funkcja
static inline: sprawdzana pod kątem typów, oblicza każdy argument dokładnie raz, a nowoczesne kompilatory wstawiają ją w miejsce wywołania tak samo jak makro. To właściwy zamiennik niemal każdego makra funkcyjnego. - Makro: gdy potrzebujesz
__LINE__, gdy coś musi działać, zanim istnieją typy (rozmiary tablic w starszym kodzie), gdy przełączasz kod kompilacją warunkową albo gdy generujesz powtarzalny kod.
Jedno miejsce, w którym makra pozostają bezkonkurencyjne, to rozmiary tablic, bo wynik musi być stałą czasu kompilacji:
To makro ma własne, dobrze znane zastrzeżenie: jest poprawne tylko dla prawdziwej tablicy. Przekaż wskaźnik, a tym staje się tablica przekazana do funkcji, i sizeof zmierzy wskaźnik, dając złą odpowiedź z pełnym przekonaniem i bez słowa ostrzeżenia.
Debugowanie makra
Gdy makro źle się zachowuje, nie wpatruj się w nie. Rozwiń je:
gcc -E program.c | tail -30
Podstawiony tekst od razu pokaże, czy problemem jest brakujący nawias, podwójne obliczanie, czy nazwa kolidująca z czymś innym. Kompiluj też z -Wall -Wextra: GCC i clang dodają notatki "in expansion of macro", które łączą zgłoszoną linię błędu z definicją.
Dalej: pliki nagłówkowe, w których #define i #include łączą się tak, że jeden projekt może obejmować wiele plików źródłowych bez dwukrotnego wklejania tych samych deklaracji.
Najczęściej zadawane pytania
Czym jest makro w C?
Nazwanym fragmentem tekstu, który preprocesor wstawia do kodu źródłowego przed kompilacją. #define MAX 100 sprawia, że każde późniejsze MAX staje się 100; #define SQUARE(x) ((x) * (x)) przyjmuje argumenty i wstawia je do wzorca. Makra nie mają typów i nie podlegają regułom zasięgu: to zastępowanie tekstu.
Dlaczego makra w C potrzebują tylu nawiasów?
Bo ciało jest wklejane w otaczający kod, a potem parsowane jako całość. #define SQUARE(x) x * x zamienia SQUARE(2 + 3) w 2 + 3 * 2 + 3, czyli 11 zamiast 25. Objęcie nawiasami każdego argumentu i całego ciała, ((x) * (x)), sprawia, że wstawiony tekst grupuje się tak, jak zamierzasz, niezależnie od otoczenia.
Czym różni się makro od funkcji w C?
Funkcja jest kompilowana raz, sprawdzana pod kątem typów i oblicza każdy argument dokładnie raz. Makro jest wklejane przy każdym użyciu, niczego nie sprawdza i może obliczyć argument kilka razy, więc MAX(i++, j) może zwiększyć i dwukrotnie. Wybieraj funkcje (a dla stałych const/enum); makr używaj do tego, czego funkcje nie potrafią, na przykład przechwytywania __LINE__ albo generowania kodu.
Dlaczego makro wieloliniowe otacza się do { ... } while (0)?
Żeby zachowywało się jak pojedyncza instrukcja. Samo ciało { ... } psuje się, gdy stoi po nim średnik wewnątrz if/else (średnik kończy if przedwcześnie), a zwykły ciąg instrukcji psuje się w if bez klamer. do { ... } while (0) to jedna instrukcja, która przyjmuje końcowy średnik i wykonuje swoje ciało dokładnie raz.