na obsah klávesové zkratky na hlavní stránku na menu

Preprocesor

Preprocesor umožňuje ovlivnit kód před jeho vlastním překladem. Nachází uplatnění v multiplatformním kódu a při spojování větších projektů.

První fáze překladu

Direktivy preprocesoru jsou vždy uvozeny dvojkřížkem (#). Vše od tohoto znaku až do konce řádky je chápáno jako příkaz preprocesoru. Pokud potřebujeme uvést delší příkaz, který by zabral několik řádek, můžeme příkaz zalomit použitím zpětného lomítka na konci řádky. Slovo preprocessing znamená předzpracování. Tak tomu ve skutečnosti opravdu je. Překladač totiž nejprve zpracuje veškeré příkazy preprocesoru a až poté začne s fází překladu. Preprocesorem se dá ovlivnit kód několika způsoby—vyjmutí části kódu, nahrazení části kódu a vložení kódu z externího souboru.

Připojení souboru

Už v prvním programu jsme využili preprocesor. Příkazem #include<stdio.h> jsme překladači řekli, že na toto místo v kódu si přejeme vložit obsah souboru stdio.h. Obecně můžeme pomocí příkazu include vložit na jakékoliv místo v programu obsah jiného souboru. Nicméně musíme být opatrní, abychom některý soubor nevložili víckrát, protože preprocesor toto nezkontroluje a provede jen vložení. Při následném překladu si bude překladač stěžovat, že kód obsahuje duplikátní deklarace. V jazyce C se vkládají pouze hlavičkové soubory. Soubory se zdrojovým kódem je nesmyslné vkládat, neboť překladač potřebuje znát pouze deklarace použitých funkcí, nikoliv jejich těla. Příkaz include má dvě varianty. Ta první používá špičaté závorky okolo jména souboru, tad druhá používá uvozovky. Význam se liší podle toho, v jakých adresářích se hledají příslušné soubory. Špičaté závorky jsou vyhrazeny pro systémové hlavičky a hledají se běžně v systémových adresářích (nebo v adresářích uvedených v parametrech překladače). Naproti tomu soubory uvedené v uvozovkách se hledají relativně k umístění aktuálně překládaného souboru.

// vloží obsah systémového souboru stdio.h
#include <stdio.h>

// vloží obsah hlavičky projektu
#include "mojehlavicka.h"

// ...

/*
opětovné vložení by mohlo způsobit problémy
v tomto případě se ale nic nestane z důvodu
vysvětleného níže
*/
#include <stdio.h>

Máme-li rozsáhlejší projekt, není mnohdy snadné uhlídat pořadí vkládání jednotlivých hlavičkových souborů. Velmi snadno se nám může stát, že se nám vkládání zacyklí či že vložíme jednu hlavičku dvakrát. Abychom se tomuto vyhnuli, používá se standardní konstrukce, která zabrání vícenásobnému vložení.

Podmíněný překlad

Toto je poměrně mocná technika, která umožňuje významně adaptovat kód v závislosti na prostředí, v němž je kód překládán. Nejde o nic jiného než o podmínky preprocesoru, které umí vypouštět bloky kódu. Struktura příkazu se podobá příkazu if

#ifdef <makro> nebo #ifndef <makro> nebo #if <podmínka>

// ..

#else nebo #elif <podmínka>

// ...

#endif

Příkaz funguje následovně. Nejprve se vyhodnotí podmínka—v prvních dvou případech se ptáme, zdali byla nebo nebyla dříve definovaná proměnná preprocesoru (makro), ve třetím případě se vyhodnocuje podmínka. Podmínka musí být vyhodnotitelná v době překladu a smí obsahovat pouze makra, nikoliv konstanty nebo proměnné z kódu. Je-li podmínka vyhodnocena jako pravda, vloží se do kódu blok programu až do else, elif nebo endif, pokud není uvedeno větvení. Je-li podmínka vyhodnocena jako nepravda, vloží se druhý blok kódu (je-li uveden).

Makro můžeme definovat několika způsoby. Tím nejběžnějším je použít příkaz define.

/*
makra se běžně pojmenovávají velkými písmeny
tento příkaz vytvoří nové makro PROMENNA bez přiřazené
hodnoty
*/
#define PROMENNA

// nové makro PI s hodnotou 3.1415, středník se nepíše!!
#define PI 3.1415

// definice funkčního makra
#define abs(x) (((x)>=0)?(x):-(x))

Opakem příkazu define je příkaz undef <existujici-makro>, který zruší předem definované makro. Makra se používají jako substituční členy v programu a jako řídící proměnné. Poslední uvedená definice definuje funkční makro, které se chová jako funkce (za jménem makra jsou kulaté závorky). Zde definujeme absolutní hodnotu. Důvod, proč používáme tolik závorek v těle funkčního makra, je ten, že ve fázi předzpracování se neřeší priorita operátorů, což znamená, že bez závorek se může výraz vyhodnotit chybně. V příkladu výše jsme vytvořili makro PI. Kdekoliv v kódu ho tak můžeme použít jako obyčejnou konstantu.

float y = sin(PI*x);

Před vlastním překladem se totiž všechna makra rozvinou, což znamená, že se všechny výskyty maker nahradí jejich hodnotami. Substituce probíhá na úrovni řetězců kódu, tudíž nedochází k žádné kontrole typů. Proto je možné napsat následující chybný kód.

#define MAKRO "abc"

int a = MAKRO;

Zde si bude překladač stěžovat na nekompatibilitu typů, která se schovává v použití makra. Proto se doporučuje pro konstantní hodnoty využívat konstant namísto maker. Obecně chyby způsobené špatným rozvinutím makra jsou někdy obtížně dohledatelné, protože překladač si bude stěžovat až u substituovaného kódu. Pro ladění této situace může být vhodné nechat překladač provést pouze fázi předzpracování a prohlédnout si výsledný kód (v GCC se provede parametrem -E).

Další možností, jak definovat makro, je použití parametru překladače (v GCC je to parametr -D <jmeno>=<hodnota>).

Zabránění vícenásobnému vložení hlavičky

Jedním z možných řešení výše popsaného problému je použití následující konstrukce.

#ifndef SPECIFICKE_JMENO_PRO_HLAVICKU
#define SPECIFICKE_JMENO_PRO_HLAVICKU

// kód hlavičky

#endif

Při prvním vložení ještě neexistuje uvedené makro, a tak se hlavička vloží. Při dalším pokusu už makro existuje, a tudíž se celý kód hlavičky přeskočí.