8. seminář - Práce s preprocesorem

TEACHINGZPC2

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

Upravte kód z příkladu výše, tak aby se rozeznávaly 4 různé jazyky pro chybovou hlášku.
Napište makro power_3(x), které bude počítat třetí mocninu. Vyzkoušejte ho na vyrazech: power_3(3), power_3(i), power_3(2 + 3), power_3(i * j + 2)
Definujte makro je small_letter(c), které vrátí 0 není-li znak malé písmeno a 1, pokud je.
Vytvořte konstantu NAME obsahující řetězec a pak tuto hodnotu vypište pomocí funkce printf následovně.

    printf( ... ) // "Ahoj moje jmeno je NAME