K čemu slouží preprocesor
Předzpracovává (upravuje) zdrojový kód tak, aby měl překladač snadnější práci. Například zajišťuje vynechání komentářů, vkládání dalších souborů a rozvíjí makra. Výsledkem je textový soubor, který se dál předáví Překladači (compileru).
Řádky určené ke zpracování preprocesorem začínají znakem #.
Preprocesor jste zatím používali nevědomky (#include
, #define
).
Makra
Makra bez parametru
Makra bez parametrů nazýváme konstanty a pomocí nich zbavujeme kód "magických konstant".
Příklad
// Uvažujme výpočet
int circuit = 2 * 3.14 * r;
// Pokud konstantu 3.14 pouzivame na vice mistech v programu,
// tak je vhodne definovat konstantu pomoci #define
#define NAME value
// Definice konstanty PI
#define PI 3.14 // POZOR nepiseme ;
// Pouziti konstanty
int circuit = 2 * PI * r;
Pokud v kódu použijeme konstantu PI, tak preprocesor nahradí každý výskyt Pi hodnotou 3.14. Platnost konstanty je od místa definice až po konec souboru. Pokud chceme změnit hodnotu konstanty, tak musíme nejprvě konstantu oddefinovat a pak ji definovat znovu.
// Definice konstanty PI
#define PI 3.14
// Smazani definice PI
#undef PI
// Opetovna definice konstanty
#define PI 3.14159265359
V případě dlouhé konstanty můžeme definici rozdělit na více řádků a to pomocí znaku \.
#define LONG_CONSTANT 2.3457684587\
9776765877
Makra s parametry
Pokud se v programu použiváme funkci která je tvořena malým počtem příkazů, tak místo definice funkce můžeme použít makro. Protoze expanze makra se děje před překladem programu a jak vím volání funkce je poměrně drahá záležitost. Výhodou je tedy vyšší rychlost a nevýhodou je nemožnost použít rekurzi.
#define name(arg1, arg2, ..., argn) body
Na příkladu níže si ukážeme některé problémy které mohou nastat pří špatné definici maker.
#define add(a, b) a + b
int result = add(3, 7 + 8);
// Makro add se prepise na
int result = 3 + 7 + 8;
// Definujeme si makro na soucin
#define multiply(a, b) a * b
int result = multiply(3 + 4, 7 + 8);
// Makro multiply se přepise na
int result = 3 + 4 * 7 + 8;
// To ale neni to co jsme si prestavovali -> Problem s prioritami
// RESENI
// Uzavorkujeme jednotlive parametry makra a i cely vyraz
#define multiply(a, b) ((a) * (b))
// Makro multiply se nyni přepise spravne
int result = ((3 + 4) * (7 + 8));
// Problem s dvojim vyhodnocenim
#define power_2(x) ((x) * (x))
int i = 1;
power_2(++i);
// Makro se přepíše na
((++i) * (++i));
Jak výřešit problém s dvojím vyhodnocením? Použít funkci, nebo se vyhýbat argumenům s vedlejším efektem
Podmíněný překlad
Podmíněný překlad používáme pro dočasné vynechání části kódu při kompilaci. Typicky se používá pro vynechání ladicích částí programu, překlad platformově závislých částí zdrojového kódu či dočasné odstranění (zakomentování) větší části zdrojového kódu.
#if condition
// code
#endif
// Zdrojovy kod je prelozen pouze pokud je podminka platna.
#if condition1
// code 1
#elif condition2
// code 2
...
#else
// code n
#endif
Podmíněný překlad řızený konstantním výrazem
#if constant
// code 1
#else
// code 2
#endif
// Nebo
#if constant
// code 1
#elif constant2
// code 2
#else
// code 3
#endif
// Priklad
#define ENG 1
#if ENG
#define ERROR ”error”
#else
#define ERROR ”chyba”
#endif
// K dispozici je i direktiva #ifdef a #ifndef.
// #ifdef NAME znamená, že kód následující za touto podmínkou se vykoná
// jen pokud je makro NAME definováno (obdobně) #ifndef NAME, pokud NAME není definováno).
#define ENG 1
#ifdef ENG
#define ERROR ”error”
#else
#define ERROR ”chyba”
#endif
// #ifdef a #ifndef testují existenci jednoho makra. Pro složitější podmínky je
// potřeba použítt operátor defined a logické spojky. Viz následující modifikace
// příkladu.
#define ENG 1
#if defined(ENG) && ENG
#define ERROR ”error”
#else
#define ERROR ”chyba”
#endif
// Na rozdíl od #ifdef a #ifndef, je možné použít složitější větvení pomocí #elif.
#define ENG 1
#if defined(ENG) && ENG
#define ERROR ”error”
#elif !defined (ENG)
#define ENG 0
#define ERROR ”chyba”
#else
#define ERROR ”chyba”
#endif
Vkládání souborů
// Muzeme vlozit bud soubor se systemove knihovny a nebo nami vytvoreny
#include <name> // hledá v systémovém adresáři. Používá se pro vkládání systémových knihoven.
#include "name" // hledá soubor ve stejném adresáři, ve kterém je uložen ”volající“ program. Používá se zejména pro vkládání souborů, které jsme vytvéřeli sami.
Úkoly
NAME
obsahující řetězec a pak tuto hodnotu vypište pomocí funkce printf následovně.
printf( ... ) // "Ahoj moje jmeno je NAME