C preprocesor

C/C++ preprocessor ( angl.  preprocessor , preprocessor) - program , který připravuje kód programu v C / C++ pro kompilaci .

Základní funkce preprocesoru

Preprocesor dělá následující:

Podmíněná kompilace vám umožňuje vybrat, který kód se má zkompilovat na základě:

Kroky preprocesoru:

Jazyk preprocesoru C/C++ není Turing kompletní, už jen proto, že je nemožné zablokovat preprocesor pomocí direktiv. Viz rekurzivní funkce (teorie vyčíslitelnosti) .

Syntaxe direktiv

Direktiva preprocesoru (příkazový řádek) je řádek ve zdrojovém kódu, který má následující formát: #ключевое_слово параметры:

Seznam klíčových slov:

Popis direktiv

Vkládání souborů (#include)

Když jsou nalezeny direktivy #include "..."a #include <...>, kde "..." je název souboru, preprocesor načte obsah zadaného souboru, provede direktivy a substituce (substituce), nahradí direktivu #includedirektivou #linea obsah zpracovaného souboru.

Hledání #include "..."souboru se provádí v aktuální složce a složkách zadaných na příkazovém řádku kompilátoru. Hledání #include <...>souboru se provádí ve složkách obsahujících soubory standardní knihovny (cesty k těmto složkám závisí na implementaci kompilátoru).

Pokud je nalezena direktiva , která #include последовательность-лексем neodpovídá žádné z předchozích forem, považuje posloupnost tokenů za text, který by v důsledku všech makro substitucí měl dát #include <...>nebo #include "...". Takto vygenerovaná směrnice bude dále interpretována v souladu s obdrženým formulářem.

Zahrnuté soubory obvykle obsahují:

Direktiva #includeje obvykle uvedena na začátku souboru (v hlavičce), takže zahrnuté soubory se nazývají hlavičkové soubory .

Příklad zahrnutí souborů ze standardní knihovny C .

#include <math.h> // zahrnout deklarace matematických funkcí #include <stdio.h> // zahrnout deklarace I/O funkce

Použití preprocesoru je považováno za neefektivní z následujících důvodů:

  • pokaždé, když jsou zahrnuty soubory, jsou provedeny příkazy a substituce (substituce); kompilátor by mohl uložit výsledky předběžného zpracování pro budoucí použití;
  • vícenásobnému zahrnutí stejného souboru je třeba zabránit ručně pomocí direktiv podmíněné kompilace; kompilátor mohl tento úkol provést sám.

Počínaje 70. léty se začaly objevovat metody nahrazující vkládání souborů. Jazyky Java a Common Lisp používají balíčky (klíčové slovo package) (viz balíček v Javě ),  Pascal používá angličtinu.  jednotky (klíčová slova unita uses), v modulech Modula , OCaml , Haskell a Python  . Navrženo k nahrazení jazyků C a C++ , D používá klíčová slova a . moduleimport

Konstanty a makra #define

K definování malých částí kódu se používají konstanty a makra preprocesoru .

// konstanta #define BUFFER_SIZE ( 1024 ) // makro #define NUMBER_OF_ARRAY_ITEMS( pole ) ( sizeof( pole ) / sizeof( *(pole) ) )

Každá konstanta a každé makro je nahrazeno odpovídající definicí. Makra mají parametry podobné funkcím a používají se ke snížení režie volání funkcí v případech, kdy malé množství kódu, který funkce volá, stačí k tomu, aby způsobil znatelný výkon.

Příklad. Definice makra max , které má dva argumenty : aab .

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

Makro se volá stejně jako každá funkce.

z = max ( x , y );

Po nahrazení makra bude kód vypadat takto:

z = ( ( x ) > ( y ) ? ( x ) : ( y ) );

Spolu s výhodami použití maker v jazyce C například pro definování generických datových typů nebo ladicích nástrojů však také poněkud snižují efektivitu jejich použití a mohou dokonce vést k chybám.

Pokud jsou například f a g  dvě funkce, volání

z = max ( f (), g () );

nevyhodnotí f() jednou a g() jednou a vloží největší hodnotu do z , jak byste mohli očekávat. Místo toho bude jedna z funkcí vyhodnocena dvakrát. Pokud má funkce vedlejší účinky, je pravděpodobné, že její chování bude jiné, než se očekávalo.

Makra C mohou být jako funkce, do určité míry vytvářejí novou syntaxi a mohou být také rozšířena o libovolný text (ačkoli kompilátor C vyžaduje, aby byl text v bezchybném kódu C nebo formátován jako komentář), ale mají určitá omezení. jako softwarové struktury. Například makra podobná funkcím mohou být nazývána jako „skutečné“ funkce, ale makro nelze předat jiné funkci pomocí ukazatele, protože samotné makro nemá žádnou adresu.

Některé moderní jazyky obvykle nepoužívají tento druh metaprogramování pomocí maker jako doplňování řetězců znaků, spoléhají na automatické nebo ruční zapojení funkcí a metod, ale místo toho jiné způsoby abstrakce, jako jsou šablony , generické funkce nebo parametrický polymorfismus . Zejména inline funkce jednomu z hlavních nedostatků maker v moderních verzích C a C++, protože inline funkce poskytuje výhodu maker při snižování režie volání funkce, ale její adresa může být předána v ukazateli pro nepřímé volá nebo se používá jako parametr. Stejně tak problém vícenásobného vyhodnocení uvedený výše v makru max je pro vestavěné funkce irelevantní.

Konstanty #define můžete nahradit výčty a makra funkcemi inline.

Operátoři # a ##

Tyto operátory se používají při vytváření maker. Operátor # před parametrem makra jej uzavírá do dvojitých uvozovek, například:

#define make_str( bar ) # bar printf ( make_str ( 42 ) );

preprocesor se převede na:

printf ( "42" );

Operátor ## v makrech zřetězí dva tokeny, například:

#define MakePosition( x ) x##X, x##Y, x##Width, x##Height int MakePosition ( Object );

preprocesor se převede na:

int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; Formální popis makro substitucí

1) Řídicí řádek následujícího formuláře nutí preprocesor nahradit identifikátor sekvencí tokenů ve zbytku textu programu:

#define identifikátor token_sequence

V tomto případě jsou prázdné znaky na začátku a na konci sekvence tokenů zahozeny. Opakovaný řádek #define se stejným identifikátorem je považován za chybu, pokud sekvence tokenů nejsou identické (neshody v mezerách nezáleží).

2) Řetězec následujícího tvaru, kde mezi prvním identifikátorem a úvodní závorkou nesmí být žádné mezery, je definice makra s parametry určenými seznamem identifikátorů.

#define identifikátor(seznam_identifikátorů) sekvence_tokenů

Stejně jako v prvním formuláři jsou prázdné znaky na začátku a na konci sekvence tokenů zahozeny a makro lze předefinovat pouze se stejným seznamem parametrů čísla a názvu a stejnou sekvencí tokenů.

Řídicí řádek, jako je tento, říká preprocesoru, aby „zapomněl“ definici daného identifikátoru:

#undef identifikátor

Použití direktivy #undef na dříve nedefinovaný identifikátor se nepovažuje za chybu.

{

  • Pokud byla definice makra uvedena ve druhém formuláři, pak jakýkoli další řetězec znaků v textu programu sestávající z identifikátoru makra (případně následovaného znaky mezer), úvodní závorky, seznamu tokenů oddělených čárkami a uzavírací závorka, představuje vyvolání makra.
  • Argumenty volání makra jsou tokeny oddělené čárkami a čárky uzavřené v uvozovkách nebo vnořených závorkách se neúčastní separace argumentů.
  • (!)Při seskupování argumentů se v nich neprovádí rozšíření makra.
  • Počet argumentů ve volání makra musí odpovídat počtu parametrů definice makra.
  • Po extrahování argumentů z textu jsou prázdné znaky, které je obklopují, zahozeny.
  • Potom je v nahrazovací sekvenci makro tokenů každý neuvedený parametr identifikátoru nahrazen odpovídajícím skutečným argumentem z textu.
  • (!)Pokud parametru v nahrazovací sekvenci nepředchází znak # a ani před ním ani za ním znak ##, pak jsou tokeny argumentů kontrolovány na přítomnost volání maker v nich; pokud nějaké existují, pak se v něm před dosazením argumentu provede expanze odpovídajících maker.

Proces substituce je ovlivněn dvěma speciálními operátorskými znaky.

  • Za prvé, pokud parametru v náhradním řetězci tokenů předchází znak #, pak se kolem odpovídajícího argumentu umístí řetězcové uvozovky (") a poté se identifikátor parametru spolu se znakem # nahradí výsledným řetězcovým literálem .
    • Zpětné lomítko se automaticky vloží před každý znak " nebo \, který se vyskytuje kolem nebo uvnitř řetězce nebo znakové konstanty.
  • Za druhé, pokud posloupnost tokenů v definici makra jakéhokoli druhu obsahuje znak ##, pak je ihned po nahrazení parametru spolu se znaky prázdných míst, které jej obklopují, zahozen, v důsledku čehož jsou sousední tokeny zřetězeny, čímž se vytvoří nový token.
    • Výsledek je nedefinovaný, pokud jsou tímto způsobem vygenerovány neplatné jazykové tokeny nebo když výsledný text závisí na pořadí, ve kterém je operace ## aplikována.
    • Kromě toho se znak ## nemůže objevit ani na začátku, ani na konci náhradní sekvence žetonů.

}

  • (!) V makrech obou typů je nahrazující sekvence tokenů znovu prohledávána při hledání novýchdefinovaných identifikátorů.
  • (!)Pokud však již byl některý identifikátor nahrazen v aktuálním procesu rozšiřování, opětovné objevení takového identifikátoru nezpůsobí jeho nahrazení; zůstane nedotčena.
  • (!)I když linka volání rozšířeného makra začíná znakem #, nebude brána jako direktiva preprocesoru.

Vykřičník (!) označuje pravidla zodpovědná za rekurzivní vyvolání a definice.

Příklad rozšíření makra #define cat( x, y ) x ## y

Volání makra "cat(var, 123)" bude nahrazeno "var123". Volání "cat(cat(1, 2), 3)" však nepřinese požadovaný výsledek. Zvažte kroky preprocesoru:

0: cat( cat( 1, 2), 3) 1: cat( 1, 2) ## 3 2: kat( 1, 2)3

Operace "##" zabránila správnému rozšíření argumentů druhého volání "cat". Výsledkem je následující řetězec tokenů:

kočka ( 1 , 2 ) 3

kde ")3" je výsledkem zřetězení posledního tokenu prvního argumentu s prvním tokenem druhého argumentu, není platný token.

Druhou úroveň makra můžete určit následovně:

#define xcat( x, y ) cat( x, y)

Volání "xcat(xcat(1, 2), 3)" bude nahrazeno "123". Zvažte kroky preprocesoru:

0: xcat( xcat( 1, 2), 3) 1: cat( xcat( 1, 2), 3) 2: cat( cat( 1, 2), 3) 3: cat( 1 ## 2, 3 ) 4: kočka ( 12, 3 ) 5:12##3 6:123

Vše proběhlo v pořádku, protože operátor „##“ se nepodílel na rozšíření makra „xcat“.

Mnoho statických analyzátorů není schopno správně zpracovat makra, takže kvalita statické analýzy je snížena .

Předdefinované konstanty #define

Konstanty generované automaticky preprocesorem:

  • __LINE__je nahrazeno aktuálním číslem řádku; aktuální číslo řádku může být přepsáno direktivou #line; používá se pro ladění ;
  • __FILE__je nahrazeno názvem souboru; název souboru lze také přepsat příponou #line;
  • __FUNCTION__je nahrazeno názvem aktuální funkce;
  • __DATE__je nahrazeno aktuálním datem (v době zpracování kódu preprocesorem);
  • __TIME__je nahrazeno aktuálním časem (v době, kdy byl kód zpracován preprocesorem);
  • __TIMESTAMP__je nahrazeno aktuálním datem a časem (v době, kdy byl kód zpracován preprocesorem);
  • __COUNTER__je nahrazeno jedinečným číslem začínajícím od 0; po každé výměně se číslo zvýší o jednu;
  • __STDC__je nahrazeno 1, pokud je kompilace v souladu s jazykovou normou C;
  • __STDC_HOSTED__definované v C99 a výše; je nahrazeno 1, pokud je provedení pod kontrolou OS ;
  • __STDC_VERSION__definované v C99 a výše; pro C99 se nahrazuje číslem 199901 a pro C11 se nahrazuje číslem 201112;
  • __STDC_IEC_559__definované v C99 a výše; konstanta existuje, pokud kompilátor podporuje operace s plovoucí desetinnou čárkou IEC 60559;
  • __STDC_IEC_559_COMPLEX__definované v C99 a výše; konstanta existuje, pokud kompilátor podporuje operace s komplexními čísly IEC 60559; standard C99 zavazuje podporovat operace s komplexními čísly;
  • __STDC_NO_COMPLEX__definováno v C11; je nahrazeno 1, pokud nejsou podporovány operace s komplexními čísly;
  • __STDC_NO_VLA__definováno v C11; nahrazeno 1, pokud pole s proměnnou délkou nejsou podporována; pole s proměnnou délkou musí být podporována v C99;
  • __VA_ARGS__definovaný v C99 a umožňuje vytvářet makra s proměnným počtem argumentů.

Podmíněná kompilace

C preprocesor poskytuje schopnost kompilace s podmínkami. To umožňuje možnost různých verzí stejného kódu. Obvykle se tento přístup používá k přizpůsobení programu pro platformu kompilátoru, stav (odladěný kód lze zvýraznit ve výsledném kódu) nebo možnost přesně jednou zkontrolovat připojení souboru.

Obecně platí, že programátor potřebuje použít konstrukci jako:

# ifndef FOO_H # definovat FOO_H ...( kód hlavičkového souboru )... # endif

Tato "ochrana maker" zabraňuje dvojitému zahrnutí souboru záhlaví kontrolou existence tohoto makra, které má stejný název jako soubor záhlaví. K definici makra FOO_H dochází, když preprocesor poprvé zpracuje hlavičkový soubor. Pak, pokud je tento hlavičkový soubor znovu zahrnut, FOO_H je již definován, což způsobí, že preprocesor přeskočí celý text tohoto hlavičkového souboru.

Totéž lze provést zahrnutím následující směrnice do hlavičkového souboru:

#pragma jednou

Podmínky preprocesoru lze zadat několika způsoby, například:

# ifdef x ... #jinak ... # endif

nebo

# ifx ... #jinak ... # endif

Tato metoda se často používá v souborech hlaviček systému k testování různých schopností, jejichž definice se může lišit v závislosti na platformě; například knihovna Glibc používá makra pro kontrolu funkcí k ověření, zda je operační systém a hardware správně podporují (makra) při zachování stejného programovacího rozhraní.

Většina moderních programovacích jazyků tyto funkce nevyužívá, spoléhá se spíše na tradiční podmíněné příkazy if...then...else..., takže kompilátor má za úkol extrahovat nepotřebný kód z kompilovaného programu.

Digrafy a trigrafy

Viz digrafy a trigrafy v jazycích C/C++.

Preprocesor zpracovává digrafy „ %:“ (“ #”), „ %:%:“ (“ ##”) a trigrafy „ ??=“ (“ #”), „ ??/“ (“ \”).

Preprocesor považuje sekvenci " %:%: " za dva tokeny při zpracování kódu C a jeden token při zpracování kódu C++.

Viz také

Poznámky

Odkazy