C Programowanie / preprocesor dyrektywy i makra

dyrektywy są specjalne instrukcje skierowane do preprocesora (dyrektywy preprocesora) lub do kompilatora (dyrektywy kompilatora), w jaki sposób powinien przetwarzać część lub całość kodu źródłowego lub ustawić kilka flag na ostatecznym obiekcie i są używane do ułatwienia pisania kodu źródłowego (na przykład bardziej przenośne) i aby kod źródłowy był bardziej zrozumiały. Dyrektywy są obsługiwane przez preprocesor, który jest albo oddzielnym programem wywoływanym przez kompilator, albo częścią samego kompilatora.

#includeEdit

C ma pewne funkcje jako część języka, a niektóre inne jako część standardowej biblioteki, która jest repozytorium kodu, które jest dostępne wraz z każdym standardowym kompilatorem C. Kiedy kompilator C kompiluje Twój program, zwykle łączy go również ze standardową biblioteką C. Na przykład, po napotkaniu dyrektywy#include <stdio.h>, zastępuje ona dyrektywę zawartością stdio.plik nagłówka H.

Kiedy używasz funkcji z biblioteki, C wymaga od Ciebie zadeklarowania, czego będziesz używał. Pierwsza linia w programie jest dyrektywą wstępnego przetwarzania, która powinna wyglądać następująco:

#include <stdio.h>

powyższa linia powoduje wywołanie deklaracji C, które znajdują się w stdio.nagłówek h, który zostanie dołączony do użycia w programie. Zazwyczaj jest to realizowane przez po prostu wstawiając do programu zawartość pliku nagłówkowego o nazwie stdio.h, znajduje się w lokalizacji zależnej od systemu. Lokalizacja takich plików może być opisana w dokumentacji kompilatora. Lista standardowych plików nagłówkowych C znajduje się poniżej w tabeli nagłówków.

stdio.nagłówek h zawiera różne deklaracje wejścia / wyjścia (I/o) przy użyciu abstrakcji mechanizmów We/Wy zwanych strumieniami. Na przykład istnieje obiekt strumienia wyjściowego o nazwie stdout, który służy do wyprowadzania tekstu na standardowe wyjście, które zwykle wyświetla tekst na ekranie komputera.

Jeśli używasz nawiasów kątowych, jak w powyższym przykładzie, preprocesor jest poinstruowany, aby wyszukać plik include wzdłuż ścieżki środowiska programistycznego dla standardowych includes.

#include "other.h"

Jeśli używasz cudzysłowu (” „), oczekuje się, że preprocesor będzie szukał w dodatkowych, Zwykle zdefiniowanych przez użytkownika lokalizacjach pliku nagłówkowego i powróci do standardowych ścieżek dołączania tylko wtedy, gdy nie zostanie znaleziony w tych dodatkowych lokalizacjach. Często ten formularz zawiera wyszukiwanie w tym samym katalogu co plik zawierający dyrektywę # include.

Uwaga: powinieneś sprawdzić dokumentację środowiska programistycznego, którego używasz dla konkretnych implementacji dyrektywy #include.

HeadersEdit

The C90 standard headers list:

  • <assert.h>
  • <ctype.h>
  • <errno.h>
  • <float.h>
  • <limits.h>
  • <locale.h>
  • <math.h>
  • <setjmp.h>
  • <signal.h>
  • <stdarg.h>
  • <stddef.h>
  • <stdio.h>
  • <stdlib.h>
  • <string.h>
  • <time.h>

Headers added since C90:

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <iso646.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>
  • <wchar.h>
  • <wctype.h>

#pragmaEdit

dyrektywa pragma (pragmatic information) jest częścią standardu, ale znaczenie każdego pragma zależy od implementacji oprogramowania używanego standardu. Dyrektywa # pragma dostarcza sposobu na żądanie specjalnego zachowania od kompilatora. Dyrektywa ta jest najbardziej przydatna dla programów, które są niezwykle duże lub które muszą wykorzystać możliwości konkretnego kompilatora.

pragmy są używane w programie źródłowym.

#pragma token(s)
  1. pragma jest zwykle poprzedzona pojedynczym tokenem, który reprezentuje polecenie do wykonania przez kompilator. Powinieneś sprawdzić implementację oprogramowania standardu C, którego zamierzasz używać, aby uzyskać listę obsługiwanych tokenów. Nic dziwnego, że zestaw poleceń, które mogą pojawić się w dyrektywach # pragma jest inny dla każdego kompilatora; będziesz musiał zapoznać się z dokumentacją swojego kompilatora, aby zobaczyć, które polecenia pozwalają i co te polecenia robią.

na przykład jedna z najbardziej zaimplementowanych dyrektyw preprocesora,#pragma once po umieszczeniu na początku pliku nagłówkowego, oznacza, że plik, w którym się znajduje, zostanie pominięty, jeśli zostanie kilka razy dołączony przez preprocesor.

uwaga: istnieją inne metody do wykonania tej akcji, które są powszechnie określane jako użycie strażników include.

#defineEdit

Ostrzeżenie: makra Preprocesorowe, choć kuszące, mogą przynieść dość nieoczekiwane rezultaty, jeśli nie zostaną wykonane prawidłowo. Zawsze należy pamiętać, że makra są tekstowymi substytucjami wykonywanymi w kodzie źródłowym, zanim cokolwiek zostanie skompilowane. Kompilator nie wie nic o makrach i nigdy ich nie widzi. Może to powodować niejasne błędy, między innymi negatywne skutki. Preferuje używanie funkcji językowych, jeśli istnieją równoważne (w przykładzie użyj const int lub enum zamiast #definestałe d).

istnieją przypadki, w których makra są bardzo przydatne (patrz debug makro poniżej jako przykład).

dyrektywa #define jest używana do definiowania wartości lub makr, które są używane przez preprocesor do manipulowania kodem źródłowym programu przed jego kompilacją. Ponieważ definicje preprocesora są podstawiane zanim kompilator zacznie działać na kodzie źródłowym, wszelkie błędy wprowadzone przez # define są trudne do wyśledzenia.

zgodnie z konwencją, wartości zdefiniowane za pomocą #define są nazwane wielkimi literami. Chociaż robienie tego nie jest wymogiem, uważa się, że jest to bardzo zła praktyka. Pozwala to na łatwą identyfikację wartości podczas odczytu kodu źródłowego.

obecnie #define jest używany głównie do obsługi różnic kompilatora i platformy. Na przykład zdefiniowanie może zawierać stałą, która jest odpowiednim kodem błędu dla wywołania systemowego. Użycie # define powinno być zatem ograniczone, chyba że jest to absolutnie konieczne; instrukcje typedef i zmienne stałe mogą często wykonywać te same funkcje bezpieczniej.

Inną cechą polecenia # define jest to, że może przyjmować argumenty, co czyni go raczej użytecznym jako twórca pseudo-funkcji. Rozważ następujący kod:

#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}

generalnie dobrym pomysłem jest użycie dodatkowych nawiasów podczas używania złożonych makr. Zauważ, że w powyższym przykładzie, zmienna „x” jest zawsze w obrębie własnego zbioru nawiasów. W ten sposób zostanie obliczona w całości, zanim zostanie porównana z 0 lub pomnożona przez -1. Ponadto całe makro jest otoczone nawiasami, aby zapobiec zanieczyszczeniu go przez inny kod. Jeśli nie jesteś ostrożny, ryzykujesz, że kompilator źle zinterpretuje Twój kod.

ze względu na skutki uboczne za bardzo zły pomysł uważa się używanie funkcji makr opisanych powyżej.

int x = -10;int y = ABSOLUTE_VALUE( x++ );

gdyby ABSOLUTE_VALUE() było funkcją rzeczywistą 'x’ miałoby teraz wartość ’-9′, ale ponieważ był to argument w makrze, został on dwukrotnie rozszerzony i tym samym ma wartość -8.

przykład:

aby zilustrować niebezpieczeństwa makr, rozważ to naiwne makro

#define MAX(a,b) a>b?a:b

i Kod

i = MAX(2,3)+5;j = MAX(3,2)+5;

przyjrzyj się temu i zastanów się, jaka może być wartość po wykonaniu. Polecenia są zamieniane na

int i = 2>3?2:3+5;int j = 3>2?3:2+5;

Tak więc po wykonaniu i=8 I j=3 zamiast oczekiwanego wyniku i=j=8! Dlatego ostrzegano Cię, aby użyć dodatkowego zestawu nawiasów powyżej, ale nawet z tymi, droga jest obarczona niebezpieczeństwami. Czytnik alertów może szybko zorientować się, że jeśli a lub b zawiera wyrażenia,definicja musi być nawiasowa przy każdym użyciu a, b w definicji makra, jak to:

#define MAX(a,b) ((a)>(b)?(a):(b))

to działa,pod warunkiem, że a, b nie mają skutków ubocznych. Rzeczywiście,

i = 2;j = 3;k = MAX(i++, j++);

spowoduje K=4, i=3 I j=5. Byłoby to wysoce zaskakujące dla każdego, kto oczekuje, że MAX() będzie zachowywać się jak funkcja.

więc jakie jest właściwe rozwiązanie? Rozwiązaniem jest, aby nie używać makro w ogóle. Globalna, wbudowana funkcja, jak ta

inline int max(int a, int b) { return a>b?a:b }

nie ma żadnej z powyższych pułapek, ale nie będzie działać ze wszystkimi typami.

Uwaga: Jawna deklaracjainline nie jest tak naprawdę konieczna, chyba że definicja znajduje się w pliku nagłówkowym, ponieważ twój kompilator może wykonywać funkcje wbudowane (w GCC można to zrobić za pomocą-finline-functions lub-O3). Kompilator jest często lepszy od programisty w przewidywaniu, które funkcje są warte wprowadzenia. Ponadto wywołania funkcji nie są bardzo drogie (kiedyś były).

kompilator może zignorować słowo kluczowe inline. Jest to tylko wskazówka (z wyjątkiem tego, żeinline jest konieczne, aby umożliwić zdefiniowanie funkcji w pliku nagłówkowym bez generowania Komunikatu o błędzie, ponieważ funkcja jest zdefiniowana w więcej niż jednej jednostce tłumaczeniowej).

(#, ##)

operatory # i ## są używane z makrem #define. Użycie # powoduje, że pierwszy argument po # jest zwracany jako łańcuch w cudzysłowie. Na przykład polecenie

#define as_string( s ) # s

sprawi, że kompilator zmieni to polecenie

puts( as_string( Hello World! ) ) ;

w

puts( "Hello World!" );

używając ## łączy to, co jest przed # z tym, co jest po nim. Na przykład polecenie

#define concatenate( x, y ) x ## y...int xy = 10;...

spowoduje, że kompilator zmieni

printf( "%d", concatenate( x, y ));

w

printf( "%d", xy);

, co oczywiście wyświetli 10 na standardowe wyjście.

Możliwe jest połączenie argumentu makra ze stałym prefiksem lub przyrostkiem, aby uzyskać prawidłowy identyfikator jak w

#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )

, który zdefiniuje funkcję o nazwie my_bar(). Ale nie jest możliwe zintegrowanie argumentu makra ze stałym ciągiem znaków za pomocą operatora konkatenacji. Aby uzyskać taki efekt, można użyć właściwości ANSI C, że dwie lub więcej następujących po sobie stałych łańcuchowych są uważane za równoważne pojedynczej stałej łańcuchowej, gdy są napotkane. Korzystając z tej właściwości, można zapisać

#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )

, którą makro-procesor zamieni w

puts( "I'm eating " "fruit" " today." )

, co z kolei będzie interpretowane przez parser C jako pojedyncza stała łańcuchowa.

poniższy trik może być użyty do przekształcenia stałych liczbowych w literały łańcuchowe

#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));

jest to nieco trudne, ponieważ jest rozwijane w 2 krokach. Najpierw num2str(CONST) zostaje zastąpiony str(23), który z kolei zostaje zastąpiony "23". Może to być przydatne w poniższym przykładzie:

#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif

to daje ładną wiadomość debugowania, w tym plik i wiersz, w którym wiadomość została wydana. Jeśli jednak debugowanie nie jest zdefiniowane, komunikat debugowania całkowicie zniknie z twojego kodu. Uważaj, aby nie używać tego rodzaju konstrukcji z czymkolwiek, co ma skutki uboczne, ponieważ może to prowadzić do błędów, które pojawiają się i znikają w zależności od parametrów kompilacji.

makrosedytuj

makra nie są sprawdzane przez typ i nie oceniają argumentów. Ponadto, nie są one poprawnie przestrzegane scope, ale po prostu biorą przekazywany im łańcuch znaków i zastępują każde wystąpienie argumentu makra w tekście makra rzeczywistym łańcuchem dla tego parametru (kod jest dosłownie kopiowany do miejsca, z którego został wywołany).

przykład użycia makra:

 #include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }

— wynikiem „a” powinno być „2” (b + c = 16- > przekazane do ADD- > 16/plasterki- > wynikiem jest „2”)

Uwaga:
definiowanie makr w nagłówkach jest zwykle złą praktyką.

makro powinno być zdefiniowane tylko wtedy, gdy nie jest możliwe osiągnięcie tego samego wyniku za pomocą funkcji lub innego mechanizmu. Niektóre Kompilatory są w stanie zoptymalizować kod do tego, gdzie wywołania małych funkcji są zastępowane kodem inline, negując ewentualną przewagę prędkości.Używanie typedefs, enums i inline (w C99) jest często lepszym rozwiązaniem.

jedną z niewielu sytuacji, w których funkcje inline nie będą działać-więc jesteś zmuszony używać makr podobnych do funkcji-jest inicjalizacja stałych czasu kompilacji (statyczna inicjalizacja struktur).Dzieje się tak, gdy argumenty makra są literałami, które kompilator może zoptymalizować do innego literału.

#errorEdit

dyrektywa #error zatrzymuje kompilację. Gdy taki zostanie napotkany, standard określa, że kompilator powinien emitować diagnostykę zawierającą Pozostałe tokeny w dyrektywie. Jest to najczęściej używane do celów debugowania.

programiści używają „#error” wewnątrz bloku warunkowego, aby natychmiast zatrzymać kompilator, gdy „#if” lub „#ifdef” — na początku bloku — wykryje problem w czasie kompilacji.Zwykle kompilator pomija blok (i znajduje się w nim dyrektywa” #error”) i kontynuuje kompilację.

 #error message

#warningEdit

wiele kompilatorów obsługuje dyrektywę #warning. Gdy taki zostanie napotkany, kompilator emituje diagnozę zawierającą Pozostałe tokeny w dyrektywie.

 #warning message

#undefEdit

dyrektywa #undef undefines a macro. Identyfikator nie musi być wcześniej zdefiniowany.

#if,#else,#elif,#endif (conditionals)Edytuj

Komenda #if sprawdza, czy kontrolujące wyrażenie warunkowe ma wartość zerową lub niezerową i wyklucza lub zawiera odpowiednio blok kodu. Na przykład:

 #if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif

wyrażenie warunkowe może zawierać dowolny operator C z wyjątkiem operatorów przypisania, operatorów przyrostu i dekrementacji, operatora adresu i operatora sizeof.

jeden unikalny operator używany w przetwarzaniu wstępnym i nigdzie indziej nie jest zdefiniowanym operatorem. Zwraca 1, jeżeli nazwa makra, opcjonalnie ujęta w nawiasach, jest aktualnie zdefiniowana; 0, jeżeli nie.

polecenie #endif kończy blok rozpoczęty przez#if#ifdef lub#ifndef.

polecenie # elif jest podobne do #if, z tym że jest używane do wyodrębnienia jednego z serii bloków kodu. Na przykład:

 #if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */

#ifdef,#ifndefEdit

polecenie #ifdef jest podobne do#if, z tym, że następujący po nim blok kodu jest zaznaczony, jeśli zdefiniowano nazwę makra. Pod tym względem,

#ifdef NAME

jest odpowiednikiem

#if defined NAME

polecenie #ifndef jest podobne do #ifdef, z tym że test jest odwrócony:

#ifndef NAME

jest odpowiednikiem

#if !defined NAME

#LINEEDIT

ta dyrektywa preprocesora służy do Ustawienia nazwy pliku i numeru wiersza następującego po dyrektywie na nowe wartości. Służy do ustawiania makr__ FILE __I__ LINE__.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.