C preprocesor - C preprocessor

C preprocesor je makro preprocesor pro The C , Objective-C a C ++ počítače programovacích jazyků . Preprocesor poskytuje možnost zahrnutí hlavičkových souborů , rozšíření maker , podmíněné kompilace a řízení řádků.

V mnoha implementacích jazyka C se jedná o samostatný program vyvolaný kompilátorem jako první část překladu .

Jazyk direktiv preprocesoru souvisí jen slabě s gramatikou jazyka C, a proto se někdy používá ke zpracování jiných druhů textových souborů .

Dějiny

Preprocesor byl představen C kolem roku 1973 na naléhání Alan Snyder a také jako uznání užitečnosti mechanismů souborů začleňování dostupných BCPL a PL / I . Jeho původní verze umožňovala pouze zahrnout soubory a provést jednoduchou výměnu řetězců: #include a #define bezparametrových maker. Brzy poté byl rozšířen, většinou Mike Leskem a poté Johnem Reiserem, aby zahrnoval makra s argumenty a podmíněnou kompilací.

C preprocesor byl součástí dlouhé makrojazykové tradice v Bell Labs, kterou zahájili Douglas Eastwood a Douglas McIlroy v roce 1959.

Fáze

Předběžné zpracování je definováno prvními čtyřmi (z osmi) fází překladu specifikovanými ve standardu C.

  1. Náhrada trigrafu: Preprocesor nahradí sekvence trigrafu znaky, které představují.
  2. Spojování řádků: Řádky fyzických zdrojů, které pokračují sekvencemi řádků s novým řádkem, jsou spojeny a vytvářejí logické řádky.
  3. Tokenizace: Předzpracovatel rozdělí výsledek na předzpracování tokenů a mezer . Nahrazuje komentáře mezerami.
  4. Rozšíření makra a zpracování direktivy: Provádí se předběžné zpracování direktivních řádků, včetně zahrnutí souborů a podmíněné kompilace. Preprocesor současně rozšiřuje makra a od verze standardu C z roku 1999 zpracovává _Pragma operátory.

Včetně souborů

Jedním z nejběžnějších použití preprocesoru je zahrnutí dalšího souboru:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

Preprocesor nahradí řádek #include <stdio.h> textovým obsahem souboru 'stdio.h', který mimo jiné deklaruje printf() funkci .

To lze také napsat pomocí uvozovek, např #include "stdio.h" . Pokud je název souboru uzavřen v hranatých závorkách, je soubor hledán ve standardních cestách kompilátoru. Pokud je název souboru uzavřen v uvozovkách, je cesta hledání rozšířena o aktuální adresář zdrojového souboru. Kompilátory C a programovací prostředí mají zařízení, které umožňuje programátorovi definovat, kde lze najít soubory zahrnutí. To lze zavést pomocí příznaku příkazového řádku, který lze parametrizovat pomocí souboru makefile , aby bylo možné vyměnit například jinou sadu souborů pro různé operační systémy.

Podle konvence jsou zahrnuty soubory pojmenovány s příponou .h nebo .hpp . Neexistuje však žádný požadavek, aby to bylo dodrženo. Soubory s příponou .def mohou označovat soubory, které mají být zahrnuty vícekrát, pokaždé s rozšířením stejného opakujícího se obsahu; #include "icon.xbm" pravděpodobně bude odkazovat na obrazový soubor XBM (který je zároveň zdrojovým souborem C).

#include často nutí použití #include stráží nebo #pragma once k zabránění dvojího začlenění.

Podmíněná kompilace

K if-else směrnice #if , #ifdef , #ifndef , #else , #elif a #endif mohou být použity pro podmíněné kompilace . #ifdef a #ifndef jsou jednoduché zkratky pro #if defined(...) a #if !defined(...) .

#if VERBOSE >= 2
  printf("trace message");
#endif

Většina překladačů cílených na Microsoft Windows implicitně definuje _WIN32 . To umožňuje kompilaci kódu, včetně příkazů preprocesoru, pouze při cílení na systémy Windows. WIN32 Místo toho definuje několik překladačů . Pro takové kompilátory, které implicitně nedefinují _WIN32 makro, lze jej zadat na příkazovém řádku kompilátoru pomocí -D_WIN32 .

#ifdef __unix__ /* __unix__ is usually defined by compilers targeting Unix systems */
# include <unistd.h>
#elif defined _WIN32 /* _WIN32 is usually defined by compilers targeting 32 or 64 bit Windows systems */
# include <windows.h>
#endif

Ukázkový kód testuje, zda __unix__ je definováno makro . Pokud ano, soubor <unistd.h> je zahrnut. Jinak testuje, zda _WIN32 je místo toho definováno makro . Pokud ano, soubor <windows.h> je zahrnut.

Složitější #if příklad může použít operátory, například něco jako:

#if !(defined __LP64__ || defined __LLP64__) || defined _WIN32 && !defined _WIN64
	// we are compiling for a 32-bit system
#else
	// we are compiling for a 64-bit system
#endif

Překlad lze také způsobit selháním pomocí #error směrnice:

#if RUBY_VERSION == 190
# error 1.9.0 not supported
#endif

Makro definice a expanze

Existují dva typy maker, objektová a funkční . Objektová makra neberou parametry; funkční makra (i když seznam parametrů může být prázdný). Obecná syntaxe pro deklaraci identifikátoru jako makra každého typu je:

#define <identifier> <replacement token list>                    // object-like macro
#define <identifier>(<parameter list>) <replacement token list>  // function-like macro, note parameters

Funkce podobná prohlášení makro nesmí mít žádné mezery mezi identifikátorem a prvním, otevření, závorkách. Pokud je mezera k dispozici, bude makro interpretováno jako objektové a vše začíná od první závorky přidané do seznamu tokenů.

Definici makra lze odstranit pomocí #undef :

#undef <identifier>                                              // delete the macro

Kdykoli se identifikátor objeví ve zdrojovém kódu, je nahrazen seznamem náhradních tokenů, který může být prázdný. U identifikátoru deklarovaného jako funkční makro se nahradí pouze v případě, že následující token je také levou závorkou, která začíná seznam argumentů vyvolání makra. Přesný postup při rozšiřování funkčních maker o argumenty je jemný.

Objektová makra byla běžně používána jako součást dobré programovací praxe k vytváření symbolických jmen pro konstanty, např.

#define PI 3.14159

namísto pevně zakódovaných čísel v celém kódu. Alternativou v C i C ++, zejména v situacích, kdy je vyžadován ukazatel na číslo, je použít const kvalifikátor na globální proměnnou. To způsobí, že hodnota bude uložena v paměti, místo aby byla nahrazena preprocesorem.

Příklad funkčního makra je:

#define RADTODEG(x) ((x) * 57.29578)

To definuje převod radiánů na stupně, který lze v případě potřeby vložit do kódu, tj RADTODEG(34) . To je rozšířeno na místě, takže opakované násobení konstantou se v celém kódu nezobrazuje. Makro je zde psáno jako velká písmena, aby se zdůraznilo, že se jedná o makro, nikoli o kompilovanou funkci.

Druhý x je uzavřen v jeho vlastní dvojici závorek, aby se zabránilo možnosti nesprávného pořadí operací, když se jedná o výraz namísto jediné hodnoty. Například výraz se správně rozšíří jako ; bez závorek dává přednost násobení. RADTODEG(r + 1)((r + 1) * 57.29578)(r + 1 * 57.29578)

Podobně vnější dvojice závorek udržuje správné pořadí operací. Například se rozbalí na ; bez závorek dává přednost dělení. 1 / RADTODEG(r)1 / ((r) * 57.29578)1 / (r) * 57.29578

Pořadí expanze

funkční makro expanze nastane v následujících fázích:

  1. Stringifikační operace jsou nahrazeny textovou reprezentací seznamu nahrazení jejich argumentů (bez provedení expanze).
  2. Parametry jsou nahrazeny jejich seznamem nahrazení (bez provedení expanze).
  3. Zřetězení operace jsou nahrazeny zřetězeným výsledkem dvou operandů (bez rozšíření výsledného tokenu).
  4. Tokeny pocházející z parametrů jsou rozbaleny.
  5. Výsledné tokeny jsou rozšířeny jako obvykle.

To může přinést překvapivé výsledky:

#define HE HI
#define LLO _THERE
#define HELLO "HI THERE"
#define CAT(a,b) a##b
#define XCAT(a,b) CAT(a,b)
#define CALL(fn) fn(HE,LLO)
CAT(HE, LLO) // "HI THERE", because concatenation occurs before normal expansion
XCAT(HE, LLO) // HI_THERE, because the tokens originating from parameters ("HE" and "LLO") are expanded first
CALL(CAT) // "HI THERE", because parameters are expanded first

Speciální makra a směrnice

Určité symboly musí být definovány implementací během předzpracování. Patří mezi ně __FILE__ a __LINE__ předdefinované samotným preprocesorem, které se rozšíří do aktuálního čísla souboru a řádku. Například následující:

// debugging macros so we can pin down message origin at a glance
// is bad
#define WHERESTR  "[file %s, line %d]: "
#define WHEREARG  __FILE__, __LINE__
#define DEBUGPRINT2(...)       fprintf(stderr, __VA_ARGS__)
#define DEBUGPRINT(_fmt, ...)  DEBUGPRINT2(WHERESTR _fmt, WHEREARG, __VA_ARGS__)
// OR
// is good
#define DEBUGPRINT(_fmt, ...)  fprintf(stderr, "[file %s, line %d]: " _fmt, __FILE__, __LINE__, __VA_ARGS__)

  DEBUGPRINT("hey, x=%d\n", x);

vypíše hodnotu x , před kterou předchází číslo souboru a řádku do proudu chyb, což umožňuje rychlý přístup ke kterému řádku byla zpráva vytvořena. Všimněte si, že WHERESTR argument je zřetězen s řetězcem za ním. S hodnotami __FILE__ a __LINE__ lze s touto #line směrnicí manipulovat . #line Směrnice určuje číslo řádku a název souboru linky níže. Např:

#line 314 "pi.c"
printf("line=%d file=%s\n", __LINE__, __FILE__);

generuje funkci printf:

printf("line=%d file=%s\n", 314, "pi.c");

Debuggery zdrojového kódu odkazují také na pozici zdroje definovanou pomocí __FILE__ a __LINE__ . To umožňuje ladění zdrojového kódu, když se C používá jako cílový jazyk kompilátoru pro úplně jiný jazyk. První standard C určil, že makro __STDC__ bude definováno na 1, pokud implementace odpovídá normě ISO a jinak 0, a makro __STDC_VERSION__ definované jako číselný literál určující verzi standardu podporovanou implementací. Standardní kompilátory C ++ podporují __cplusplus makro. Překladače spuštěné v nestandardním režimu nesmí tato makra nastavovat nebo musí definovat ostatní, aby signalizovaly rozdíly.

Mezi další standardní makra patří __DATE__ aktuální datum a __TIME__ aktuální čas.

Druhé vydání standardu C, C99 , přidalo podporu pro __func__ , která obsahuje název definice funkce, ve které je obsažena, ale protože preprocesor je vůči gramatice C agnostický , musí to být provedeno v samotném kompilátoru pomocí proměnná lokální pro funkci.

Makra, která mohou mít různý počet argumentů ( variadická makra ), nejsou v C89 povolena, ale byla zavedena řadou překladačů a standardizována v C99 . Variadická makra jsou zvláště užitečná při psaní obálek na funkce, které mají proměnný počet parametrů, printf například při protokolování varování a chyb.

Jeden málo známý způsob použití preprocesoru C je známý jako X-Macros . X-Macro je soubor záhlaví . Obvykle používají příponu „.def“ namísto tradičního „.h“. Tento soubor obsahuje seznam podobných volání maker, které lze označit jako „makra komponent“. Na soubor zahrnutí je poté opakovaně odkazováno.

Mnoho překladačů definuje další nestandardní makra, i když jsou často špatně zdokumentována. Společným odkazem pro tato makra je projekt předdefinovaných maker kompilátoru C / C ++ , který uvádí „různá předdefinovaná makra kompilátoru, která lze použít k identifikaci standardů, překladačů, operačních systémů, hardwarových architektur a dokonce i základních knihoven za běhu v době kompilace ".

Striktizace tokenů

Operátor # (známý jako „operátor Stringification“) převádí token na řetězcový literál jazyka C , přičemž správně uniká uvozovkám nebo zpětným lomítkům .

Příklad:

#define str(s) #s

str(p = "foo\n";) // outputs "p = \"foo\\n\";"
str(\n)           // outputs "\n"

Chcete-li zpřísnit rozšíření argumentu makra, musíte použít dvě úrovně maker:

#define xstr(s) str(s)
#define str(s) #s
#define foo 4

str (foo)  // outputs "foo"
xstr (foo) // outputs "4"

Nelze zkombinovat argument makra s dalším textem a vše dohromady zpřísnit. Můžete však napsat řadu sousedních řetězcových konstant a stringifikovaných argumentů: kompilátor C poté zkombinuje všechny sousední řetězcové konstanty do jednoho dlouhého řetězce.

Zřetězení tokenů

Operátor ## (známý jako „operátor vkládání tokenů“) zřetězuje dva tokeny do jednoho tokenu.

Příklad:

#define DECLARE_STRUCT_TYPE(name) typedef struct name##_s name##_t

DECLARE_STRUCT_TYPE(g_object); // Outputs: typedef struct g_object_s g_object_t;

Uživatelem definované chyby kompilace

#error Směrnice vysílá zprávu přes chyby proudu.

#error "error message"

Implementace

Všechny implementace C, C ++ a Objective-C poskytují preprocesor, protože předběžné zpracování je pro tyto jazyky požadovaným krokem a jeho chování je popsáno oficiálními standardy pro tyto jazyky, například normou ISO C.

Implementace mohou poskytovat svá vlastní rozšíření a odchylky a mohou se lišit stupněm souladu s písemnými normami. Jejich přesné chování může záviset na parametrech příkazového řádku zadaných při vyvolání. Například preprocesor GNU C lze nastavit tak, aby vyhovoval více standardům dodáním určitých příznaků.

Funkce preprocesoru specifické pro kompilátor

#pragma Směrnice je směrnice kompilátor specifické , které kompilátor dodavatelé mohou používat pro své vlastní účely. Například a #pragma se často používá k potlačení konkrétních chybových zpráv, ke správě ladění haldy a zásobníku atd. Kompilátor s podporou knihovny paralelizace OpenMP může automaticky paralelizovat for smyčku s #pragma omp parallel for .

C99 představil několik standardních #pragma směrnic, které mají formu #pragma STDC ... , které se používají k řízení implementace s plovoucí desetinnou čárkou. Rovněž _Pragma(...) byla přidána alternativní forma podobná makru .

  • Mnoho implementací nepodporuje trigrafy nebo je ve výchozím nastavení nenahrazuje.
  • Mnoho implementací (včetně např. Překladačů C od GNU, Intel, Microsoft a IBM) poskytuje nestandardní direktivu pro tisk varovné zprávy na výstupu, ale nezastaví proces kompilace. Typickým použitím je varovat před použitím nějakého starého kódu, který je nyní zastaralý a je zahrnut pouze z důvodů kompatibility, např .:
    // GNU, Intel and IBM
    #warning "Do not use ABC, which is deprecated. Use XYZ instead."
    
    // Microsoft
    #pragma message("Do not use ABC, which is deprecated. Use XYZ instead.")
    
  • Některé preprocesory Unixu tradičně poskytovaly „tvrzení“, která mají malou podobnost s tvrzeními používanými v programování.
  • GCC poskytuje #include_next zřetězení záhlaví se stejným názvem.
  • Preprocesory Objective-C mají #import , což je jako, #include ale obsahuje soubor pouze jednou. Běžný pragma dodavatele s podobnou funkcí v jazyce C je #pragma jednou .

Jiná použití

Protože preprocesor C lze vyvolat samostatně z kompilátoru, se kterým je dodáván, lze jej použít samostatně v různých jazycích. Pozoruhodné příklady zahrnují jeho použití v nyní zastaralém imake systému a pro předzpracování Fortranu . Takové použití jako preprocesoru pro všeobecné účely je však omezené: vstupní jazyk musí být dostatečně podobný jazyku C. GNU Fortran kompilátor automaticky nazývá „tradiční způsob“ (viz níže) CPP před kompilací Fortran kód, pokud jsou používány určité přípony souborů. Intel nabízí prepraný procesor Fortran, fpp, pro použití s kompilátorem ifort , který má podobné schopnosti.

CPP také přijatelně pracuje s většinou montážních jazyků a jazyků podobných Algolu. To vyžaduje, aby syntaxe jazyka nebyla v rozporu se syntaxí CPP, což znamená, že žádné řádky začínající na # a dvojité uvozovky, které cpp interpretuje jako řetězcové literály, a proto je ignoruje, nemají kromě toho syntaktický význam. „Tradiční režim“ (funguje jako preprocesor pre-ISO C) je obecně tolerantnější a vhodnější pro takové použití. U složitějších případů je preferována flexibilnější varianta preprocesoru C s názvem GPP .

Preprocesor C není Turingův úplný , ale je velmi blízko: lze zadat rekurzivní výpočty, ale s pevnou horní hranicí množství provedené rekurze. Preprocesor C však není navržen tak, aby byl univerzálním programovacím jazykem, ani že nefunguje dobře. Vzhledem k tomu, že preprocesor C nemá vlastnosti některých dalších preprocesorů, jako jsou rekurzivní makra, selektivní expanze podle nabídky a vyhodnocení řetězce v podmíněných podmínkách, je ve srovnání s obecnějším makroprocesorem, jako je m4, velmi omezený .

Viz také

Reference

Zdroje

externí odkazy