Programování C/Preprocessor direktivy a makra

Směrnic jsou speciální instrukce zaměřena na preprocesoru (preprocessor směrnice) nebo kompilátor (compiler směrnice) o tom, jak je třeba zpracovat část nebo všechny vaše zdrojový kód nebo nastavit některé vlajky na výsledný objekt a slouží k psaní zdrojového kódu jednodušší (více přenosné například), a aby se zdrojový kód srozumitelnější. Direktivy jsou zpracovávány preprocesorem, což je buď samostatný program vyvolaný kompilátorem, nebo část samotného kompilátoru.

#includeEdit

C má některé funkce, jako část jazyka a některé další jako součást standardní knihovny, což je úložiště kódu, který je k dispozici spolu s každou standardní-v souladu C kompilátor. Když kompilátor C kompiluje váš program, obvykle jej také spojuje se standardní knihovnou C. Například při setkání se směrnicí #include <stdio.h> nahrazuje směrnici obsahem stdio.h hlavičkový soubor.

Když používáte funkce z knihovny, C vyžaduje, abyste deklarovali, co byste používali. První řádek v programu je předzpracování směrnice, která by měla vypadat takto:

#include <stdio.h>

výše uvedený řádek způsobí, že C prohlášení, která jsou v stdio.h záhlaví, které mají být zahrnuty pro použití ve vašem programu. Obvykle se to provádí pouhým vložením obsahu hlavičkového souboru s názvem stdio do vašeho programu.h, umístěné v místě závislém na systému. Umístění takových souborů může být popsáno v dokumentaci kompilátoru. Seznam standardních hlavičkových souborů C je uveden níže v tabulce záhlaví.

stdio.záhlaví h obsahuje různé deklarace pro vstup/výstup (I / O) pomocí abstrakce I / o mechanismů nazývaných proudy. Například existuje objekt výstupního proudu nazvaný stdout, který se používá k výstupu textu na standardní výstup, který obvykle zobrazuje text na obrazovce počítače.

Pokud používáte Úhlové závorky, jako je příklad výše, preprocesor je instruován, aby hledal soubor include podél cesty vývojového prostředí pro standard includes.

#include "other.h"

Pokud použijete uvozovky (“ „), preprocessor očekává se, že hledat v některé další, obvykle definované uživatelem, místa pro hlavičku souboru, a na podzim zpět do standardní cesty patří pouze v případě, že není nalezen v těchto dalších místech. Je běžné, že tento formulář obsahuje vyhledávání ve stejném adresáři jako soubor obsahující direktivu #include.

poznámka: měli byste zkontrolovat dokumentaci vývojového prostředí, které používáte pro jakékoli implementace specifické pro dodavatele směrnice #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

pragma (pragmatická informace) směrnice je součástí standardní, ale význam žádné pragma závisí na software, provedení standard, který je používán. Direktiva #pragma poskytuje způsob, jak požádat kompilátor o speciální chování. Tato směrnice je nejužitečnější pro programy, které jsou neobvykle velké nebo které potřebují využít možností konkrétního kompilátoru.

Pragmy se používají v rámci zdrojového programu.

#pragma token(s)
  1. pragma je obvykle následována jediným tokenem, který představuje příkaz, který má kompilátor poslouchat. Měli byste zkontrolovat softwarovou implementaci standardu C, který chcete použít pro seznam podporovaných tokenů. Není divu, že sada příkazů, které se mohou objevit ve směrnicích # pragma, je pro každý kompilátor odlišná; budete muset nahlédnout do dokumentace pro kompilátor, abyste zjistili, které příkazy povoluje a co tyto příkazy dělají.

například, jeden z nejvíce implementován preprocesor, direktivy, #pragma once když umístěn na začátku záhlaví souboru, označuje, že soubor, kde je umístěna, bude přeskočen, pokud jsou zahrnuty několikrát preprocesoru.

poznámka: k provedení této akce existují jiné metody, které se běžně označují jako použití zahrnují stráže.

#defineEdit

UPOZORNĚNÍ: Preprocesoru makra, i když lákavé, může produkovat docela nečekané výsledky, ne-li provedeno správně. Vždy mějte na paměti, že makra jsou textové substituce provedené ve zdrojovém kódu dříve, než je cokoli kompilováno. Kompilátor o makrech nic neví a nikdy je neuvidí. To může způsobit nejasné chyby, mimo jiné negativní účinky. Raději používat funkce jazyka, pokud jsou ekvivalentní (V příkladu použití const int nebo enum místo #defined konstanty).

to znamená, Že existují případy, kde makra jsou velmi užitečná (viz debug makro níže pro příklad).

direktiva # define se používá k definování hodnot nebo maker, které preprocesor používá k manipulaci se zdrojovým kódem programu před jeho kompilací. Protože definice preprocesoru jsou nahrazeny dříve, než kompilátor působí na zdrojový kód, jakékoli chyby, které jsou zavedeny pomocí # define, je obtížné dohledat.

konvencí jsou hodnoty definované pomocí # define pojmenovány velkými písmeny. Ačkoli to není požadavek, považuje se za velmi špatnou praxi dělat jinak. To umožňuje snadno identifikovat hodnoty při čtení zdrojového kódu.

dnes se #define používá především pro zpracování rozdílů kompilátoru a platformy. Např. define může obsahovat konstantu, což je vhodný kód chyby pro systémové volání. Použití #define by tedy mělo být omezeno, pokud to není nezbytně nutné; příkazy typedef a konstantní proměnné mohou často provádět stejné funkce bezpečněji.

Další vlastností příkazu # define je to, že může přijímat argumenty, což je spíše užitečné jako tvůrce pseudo-funkcí. Zvažte následující kód:

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

při použití komplexních Maker je obecně vhodné použít další závorky. Všimněte si, že ve výše uvedeném příkladu je proměnná “ x “ vždy ve své vlastní sadě závorek. Tímto způsobem bude vyhodnocen jako celek, než bude porovnán s 0 nebo vynásoben -1. Celé makro je také obklopeno závorkami, aby se zabránilo kontaminaci jiným kódem. Pokud si nejste opatrní, riskujete, že kompilátor nesprávně interpretuje váš kód.

vzhledem k nežádoucím účinkům se považuje za velmi špatný nápad používat makro funkce, jak je popsáno výše.

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

Pokud ABSOLUTE_VALUE() jsou reálné funkce “ x “ by nyní mít hodnotu ‚-9‘, ale protože to byl argument v makru byl dvakrát rozšířen, a proto má hodnotu -8.

Příklad:

Pro ilustraci nebezpečí makra, zvažte to naivní makro

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

kód

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

podívejte se na to a zvažte, co hodnota po provedení by mohlo být. Příkazy se změní na

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

takže po provedení i=8 A j=3 místo očekávaného výsledku i=j=8! To je důvod, proč jste byli varováni, abyste použili další sadu závorek výše, ale i s nimi, silnice je plná nebezpečí. Upozornění čtenář může rychle uvědomit, že pokud a nebo b obsahuje výrazy, definice musí parenthesize každém použití a,b v definici makra, jako je tento:

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

Tento postup funguje, pokud a,b nemají žádné vedlejší účinky. Ve skutečnosti by

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

vedlo k k=4, i=3 A j=5. To by bylo velmi překvapivé pro každého, kdo očekává, že se MAX() bude chovat jako funkce.

jaké je tedy správné řešení? Řešením je nepoužívat makro vůbec. Globální inline funkce, jako je tato

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

nemá žádné z výše uvedených úskalí, ale nebude fungovat se všemi typy.

POZNÁMKA: explicitní inline prohlášení není opravdu nutné, pokud definice je v souboru záhlaví, protože kompilátor může inline funkce pro vás (s gcc to může být provedeno s -finline-functions nebo -O3). Kompilátor je často lepší než programátor při předpovídání, které funkce stojí za to inlining. Také volání funkcí nejsou opravdu drahé (bývaly).

kompilátor je ve skutečnosti volně ignorovatinline Klíčové slovo. Je to jen tip (kromě toho, že inline je nezbytné, aby funkce musí být definována v záhlaví souboru bez generování chybová zpráva vzhledem k funkci je definován ve více než jedné překladové jednotky).

(#,##)

operátory # a ## se používají s makrem # define. Použití # způsobí, že první argument po # bude vrácen jako řetězec v uvozovkách. Například příkaz

#define as_string( s ) # s

bude kompilátor přelomu tohoto příkazu

puts( as_string( Hello World! ) ) ;

do

puts( "Hello World!" );

Pomocí ## spojí to, co je před ## s tím, co je po ní. Například příkaz

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

bude kompilátor obrátit

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

do

printf( "%d", xy);

, která bude, samozřejmě, displej 10 na standardní výstup.

je možné zřetězit makro argument s konstantní prefix nebo suffix, získat platný jako identifikátor

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

, která bude definovat funkce nazývá my_bar(). Ale není možné integrovat makro argument do konstantního řetězce pomocí operátora zřetězení. S cílem získat takový účinek, lze použít ANSI C vlastnost, že dvě nebo více po sobě jdoucích řetězcové konstanty jsou považovány za ekvivalentní k jeden řetězec konstantní, když se setkali. Pomocí této vlastnosti, může napsat

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

makro-procesor se promění

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

což bude vykládána C parser jako jeden řetězec konstantní.

následující trik může být použit k otočení číselné konstanty do řetězcové literály

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

Toto je trochu složitější, protože je rozšířen ve 2 krocích. První num2str(CONST) nahrazen str(23), což je nahrazen "23". To může být užitečné v následujícím příkladu:

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

tím získáte pěknou ladicí zprávu včetně souboru a řádku, kde byla zpráva vydána. Pokud ladění není definováno, zpráva o ladění zcela zmizí z vašeho kódu. Dávejte pozor, abyste tento druh konstrukce nepoužívali s ničím, co má vedlejší účinky, protože to může vést k chybám, které se objevují a mizí v závislosti na parametrech kompilace.

macrosEdit

makra nejsou typově kontrolována, takže nehodnotí argumenty. Také, oni nemají poslouchat rozsah správně, ale jednoduše vzít řetězec předán k nim a nahradit každý výskyt makro argument v textu makra s vlastní řetězec pro tento parametr (kód je doslova zkopírován do umístění, to bylo voláno).

příklad o tom, jak používat 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; }

– výsledek „a“ by mělo být „2“ (b + c = 16 -> prošel PŘIDAT -> 16 / PLÁTKY -> výsledek je „2“)

POZNÁMKA:
To je obvykle špatné praxe definovat makra v záhlaví.

makro by mělo být definováno pouze v případě, že není možné dosáhnout stejného výsledku pomocí funkce nebo jiného mechanismu. Některé kompilátory jsou schopny optimalizovat kód tam, kde jsou volání na malé funkce nahrazena vloženým kódem, což neguje jakoukoli možnou výhodu rychlosti.Použití typedefs, enums a inline (v C99) je často lepší volbou.

jednou z mála situací, kdy inline funkce nebudou fungovat – takže jste nuceni místo toho používat funkční makra – je inicializace časových konstant kompilace (statická inicializace struktur).K tomu dochází, když argumenty makra jsou literály, které kompilátor může optimalizovat na jiný doslov.

#errorEdit

direktiva # error zastaví kompilaci. Pokud se vyskytne, standard specifikuje, že kompilátor by měl emitovat diagnostiku obsahující zbývající tokeny ve směrnici. To se většinou používá pro účely ladění.

Programátoři použít „#error“ v podmíněném bloku, aby okamžitě zastavily kompilátor, když „#“ nebo „#ifdef“, na začátku bloku — detekuje kompilace-time problém.Normálně kompilátor přeskočí blok (a direktivu“ #error “ v něm) a kompilace pokračuje.

 #error message

#warningEdit

Mnoho kompilátory podporují #varování směrnice. Když se objeví, kompilátor vydá diagnostiku obsahující zbývající tokeny ve směrnici.

 #warning message

#undefEdit

#undef směrnice undefines makro. Identifikátor nemusí být dříve definován.

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

příkaz # if kontroluje, zda řídící podmíněný výraz vyhodnotí na nulu nebo nenulový, a vylučuje nebo obsahuje blok kódu. Například:

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

podmíněný výraz může obsahovat libovolné C provozovatele s výjimkou operátory přiřazení, zvyšovat, a dekrementační operátory, adresa provozovatele, a sizeof operátor.

jeden jedinečný operátor používaný v předzpracování a nikde jinde není definovaný operátor. Vrací 1, pokud je aktuálně definován název makra, volitelně uzavřený v závorkách; 0, pokud ne.

#endif příkaz končí blok začal tím #if#ifdef nebo #ifndef.

příkaz # elif je podobný #if, kromě toho, že se používá k extrahování jednoho z řady bloků kódu. Např.:

 #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

#ifdef příkaz je podobný #if, kromě toho, že blok kódu po je vybrané, pokud název makra je definována. V tomto ohledu,

#ifdef NAME

je ekvivalentní

#if defined NAME

#ifndef příkaz je podobný #ifdef, kromě toho, že test je obrácen:

#ifndef NAME

je ekvivalentní

#if !defined NAME

#lineEdit

Tato direktiva preprocesoru je použita se nastavit název souboru a řádek číslo řádku následující směrnice tak, aby nové hodnoty. To se používá k nastavení__ soubor __a__ řádek _ _ makra.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.