Programovanie‎ > ‎Operátory‎ > ‎

Operátory preprocesora

# dvojitý krížik - direktíva pre preprocesor

Operátor alebo znak # je signálom pre preprocesor. Preprocesor sa spustí pri každom spustení kompilátora. Prejde si zdrojový kód a nájde riadky, ktoré začínajú symbolom dvojitého krížika (#). S týmito riadkami pracuje ešte pred spustením samotného kompilátora. Preprocesor spracováva vstupný text ako text, prevádza v ňom textové zmeny a jeho výstupom je opäť text. Od preprocesora teda nemôžeme čakať kontrolu syntaxu a ani typovú kontrolu. Preprocesor spracováva hlavičkové súbory, rozvíja makrá, neprepúšťa komentáre a umožňuje prevádzať podmienený preklad zdrojového textu.

Zdôraznime ešte jednu dôležitú skutočnosť. Direktíva preprocesoru nie je príkaz pre programovací jazyk a preto ju neukončujeme bodkočiarkou. A naviac musí byť v riadku prvým znakom a za ním by nemala byť medzera.

Príkaz #include je inštrukcia pre preprocesor, ktorá mu hovorí: „To, čo nasleduje, je názov súboru. Nájdi tento súbor a vlož jeho obsah priamo na toto miesto programu.“ Týmto spôsobom sa definujú externé zdrojové kódy - knižnice.

#include <subor.h>   // vloženie textu zo špecifikovaného súboru zo systémového adresára
#include "subor.h"   // vloženie textu zo špecifikovaného súboru v adresári užívateľa

Makrá bez parametrov, príkaz #define, sú známejšie pod názvom symbolické konštanty. Kľúčom nech je skutočnosť, že makro, na rozdiel od symbolickej konštanty, má argumenty. Ich definovanie a oddefinovanie môžeme syntakticky popísať takto:

#define MENO_MAKRA [sekvencia]
#undef MENO_MAKRA

kde

  meno_makra predstavuje meno (identifikátor) makra
  sekvencia je nepovinný súvislý reťazec

Pri svojej činnosti prehľadáva preprocesor vstupný text a pri výskyte reťazca meno_makra prevádza jeho nahradenie reťazcom sekvencia. Tejto činnosti sa hovorí rozvoj (expanzia) makra. Z tohoto popisu je jasné, prečo sa preprocesoru niekedy zjednodušene hovorí makroprocesor.

#define DHT22_PIN 7        // reťazec DHT22_PIN zameň za reťazec 7
#define TFT_DC 9           // reťazec TFT_DC zameň za reťazec 9
#define TFT_CS 10          // reťazec TFT_CS zameň za reťazec 10

DHT22 myDHT22(DHT22_PIN);  // preprocesor z toho vytvorí pre kompilátor toto: DHT22 myDHT22(7);
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); // Adafruit_ILI9341 tft = Adafruit_ILI9341(10, 9);

V zdrojovom texte sa neodvolávame na nič nehovoriace čísla, ale na vhodne symbolicky pomenované makrá. Program to nielen sprehľadní, ale prípadnú zmenu hodnoty makra prevedieme na jednom mieste, preto sa uvádzajú na začiatku programu. Treba ale myslieť na túto vec. Ak urobíte niečo takéto:

#define NAMERANA_HODNOTA "Posledna namerana hodnota je:"

a použijete vo svojom zdrojovom kóde na 25 miestach NAMERANA_HODNOTA, dostanete 25 kópií reťazca "Posledna namerana hodnota je:" v pamäti SRAM!!! Miesto toho v tomto prípade je vhodné použiť Const char pole, čo má za následok iba jednu kópiu reťazca v pamäti SRAM. Alebo treba použiť PROGMEM.

A taktiež ešte si pripomeňme najčastejšie chyby pri zápise:

#define LED_PIN = 3   // toto je špatne, nesmie tam byt znamienko rovnosti =
#define LED_PIN 3;    // aj toto je špatne, nesmie tam byt bodkočiarka ;

Názvy symbolických konštánt treba písať vždy VEĽKÝMI PÍSMENAMI. Názov je od hodnoty oddelený aspoň jednou medzerou, resp. sa zarovnáva na stĺpec (tabelátor) keď ich je viac pod sebou. Za hodnotou by mal byť komentár napr.:

#define ILI9341_BLACK       0x0000      //   0,   0,   0  čierna
#define ILI9341_YELLOW      0xFFE0      // 255, 255,   0  žltá 
#define ILI9341_DARKGREEN   0x03E0      //   0, 128,   0  tmavozelená
#define ILI9341_WHITE       0xFFFF      // 255, 255, 255  biela 
#define ILI9341_TFTWIDTH    240         // šírka TFT
#define ILI9341_TFTHEIGHT   320         // výška TFT

Preprocesor môže behom svojej činnosti vyhodnocovať, či je nejaké makro definované alebo nie. Pri použití kľúčového slova preprocesoru defined potom môžeme spájať také vyhodnotenia do rozsiahlejších logických výrazov. Argument nemusí byť uzavretý do zátvoriek. Môže sa však vyskytnúť len za #if alebo #elif. V závislosti na splnení či nesplnení podmienky môžeme určiť, či bude ohraničený úsek programu ďalej spracovaný alebo či bude odfiltrovaný a tak teda nebude preložený. Tejto možnosti použitia preprocesoru hovoríme podmienený preklad textu v závislosti na tom, či je makro definované.

Test existencie definície symbolickej konštanty pomocou direktív #ifdef alebo #ifndef umožňuje zisťovať existenciu iba jedného symbolu (mena konštanty). Aby bolo možné vytvárať pri podmienenej kompilácii logické výrazy z viac symbolov, je nutné použiť operátor defined. Spôsob použitia operátoru defined ukazujú nasledujúce tri ekvivalentne príkazy:

pre direktívu #ifdef	           pre direktívu #ifndef
#ifdef MENO_MAKRA	           #ifndef MENO_MAKRA
#if defined MENO_MAKRA	           #if !defined MENO_MAKRA
#if defined(MENO_MAKRA)	           #if !defined(MENO_MAKRA)  

Vždy musí byť jasné, kde podmienená časť zdrojového textu začína a kde končí. Preto nesmieme zabúdať na #endif či #elif. Podmienené časti musia byť ukončené a obmedzené v rámci jedného zdrojového textu. Inak oznámi preprocesor chybu.

#if defined MENO_MAKRA    // podmienka
  #define INE_MAKRO       // tato definicia je zavisla od vysledku podmienky
#endif                    // ukoncenie podmienky

Tu máme príklady, kde podľa typu hardvéru si zadefinujeme vždy rôzne knižnice.

#if defined(ESP8266)
  #include <pgmspace.h>
#else
  #include <avr/pgmspace.h>
#endif
#if defined(__AVR__)
  #include <util/delay.h>
#endif
#if defined (__AVR__) || defined(TEENSYDUINO) || defined (__arm__)
  #define USE_FAST_PINIO
#endif

Podmienky veľmi pripomínajú konštrukcie jazyka C. Naviac je oproti C zavedená i podmienka #elif. Nenechajme sa však mýliť. Vyhodnotenie podmienok prevádza už preprocesor.

#error je direktívou, ktorou môžeme zaistiť výstup nami zadaného chybového hlásenia. Najčastejšie sa používa v súvislosti s podmieneným prekladom. Má formát:

#error chybove hlasenie  

kde chybové hlásenie bude súčasťou protokolu o preklade. Táto direktíva sa typicky používa na kontrolu hodnôt symbolických konštánt ovplyvňujúcich podmienenú kompiláciu. Teraz bude uvedený príklad, ktorý by mal objasniť použitie direktív #elif a #error, tak i operátoru defined.

#if defined(ARDUINO_ARCH_AVR)
  // kod pre AVR
#elif defined(ARDUINO_ARCH_SAM)
  // kod pre SAM
#else
  #error "Tato kniznica podporuje len hardver s AVR alebo SAM procesorom."
  // alebo tu môže byť kód pre nešpecifikovanú platformu
#endif

V mnohých prípadoch sa zložitejšie programy píšu tak, že obsahujú ladiace časti. To sú najčastejšie pomocné výpisy, ktoré majú uľahčiť ladenie. Tieto časti sa do programov dávajú, aj keď máme k dispozícii výkonný debuger. Je dobrým zvykom počítať už pri návrhu programu s tým, že ho bude nutné ladiť a už pri návrhu programu tieto časti do programu zaraďovať. Po odladení však nastáva typický problém - ako tieto časti (už nepotrebné) z programu odstrániť. Najjednoduchšie je ich vymazať, ale pritom sa môže stať, že zmažeme aj dôležité časti programu a tým program znefunkčníme. Tento problémom vyriešime tak, že pomocou príkazov preprocesora môžeme určiť, ktoré časti programu sa majú prekladať podmienene. To znamená, že všetky ladiace časti už pri vytváraní programu označíme ako podmienene prekladané a pri ladení ich prekladáme, ale po odladení už nie. Ladiace časti sú tak trvalou súčasťou zdrojového programu, ale sú zároveň voliteľnou súčasťou. Preprocesor teda na jediný príkaz všetky tieto časti vypustí sám, ale budú k dispozícií do budúcnosti. V prípade potreby ich jedným príkazom zase do programu zaradíme.

//#define DEBUG         // odkomentovaním sa bude vykonávať i kód napr. pre výpis na sériovú linku

#ifdef DEBUG      
  Serial.print("Testovanie kodu.");     // odošleme text na sériovú linku
#endif

Podmienený preklad riadený konštantným výrazom, čo môže byť číslo, symbolická konštanta alebo i podmienený výraz z týchto možností, má túto syntax:

#if konstantny_vyraz
    cast_1
#else
    cast_2
#endif

Prekladač bude túto časť programu spracovávať tak, že ak je hodnota konstantny_vyraz rovná 0 (FALSE, nepravda), prekladá sa iba cast_2 a v opačnom prípade (nenulová hodnota - TRUE) sa prekladá iba cast_1. Samozrejme, že časť #else a cast_2 môžu byť vynechané. Často sa podmienený preklad používa pri vývoji programov, ktoré sú síce závisle na konkrétnom hardvéri, ale mali by po minimálnych zmenách fungovať i na hardvéroch iných. Alebo podľa verzie programového prostredia.

#if ARDUINO >= 100        // podmienka verzie ARDUINO IDE 
 #include "Arduino.h"     // pre novú verziu ARDUINO IDE 1.00 a vyššie použi knižnicu Arduino.h
#else
 #include "WProgram.h"    // pre staršie verzie ARDUINO IDE použi knižnicu WProgram.h
#endif

Súbory, ktoré sa dajú vkladať pomocou príkazu #include, môžu byť ľubovolné textové súbory. Význam má samozrejme vkladanie len zdrojových súborov s príponou .C. Druhá a oveľa častejšia možnosť, je vkladanie tzv. hlavičkových súborov s príponou .h. Vkladanie hlavičkových súborov je veľmi užitočný mechanizmus, ako program pozostávajúci z viacerých súborov udržať čitateľný. Napríklad všetky definície konštánt využívané viacerými súbormi sa uvedú iba raz do súboru typu .h, ktorý sa pomocou #include pripojí do všetkých súborov, ktoré tieto definície konštánt potrebujú. To má obrovskú výhodu, že prípadná zmena konštánt sa potom uskutoční iba v jednom súbore .h a ostatné súbory, využívajúce tieto konštanty, stačí iba preložiť. Tu je príklad definície konštánt popisujúce pomery na obrazovke a sú uložené v súbore obraz.h:

#ifndef OBRAZ
#define OBRAZ

#define SIRKA_OBRAZOVKY 240
#define VYSKA_OBRAZOVKY 320

#endif    // koniec súboru obraz.h 

Vo všetkých súboroch, ktoré potrebujú pracovať s obrazovkou sa potom použije príkaz:

#include "obraz.h"

Avšak vyššie popísaný spôsob je veľmi užitočný "trik", ako zabrániť niekoľkonásobnému natiahnutiu súboru obraz.h do jedného modulu. Tento omyl sa stane veľmi ľahko, zvlášť, ak sú súbory s príkazmi #include do seba ponorené. Pokiaľ máme hlavičkové súbory vytvárané týmto spôsobom, nič sa nemôže stať ani pri niekoľkonásobnom natiahnutí súboru obraz.h do jedného súboru. Pri prvom natiahnutí sa totiž definuje symbolická konštanta OBRAZ a tá v spolupráci s príkazom#ifndef zabráni opätovnému natiahnutiu obsahu súboru.

Pokiaľ sa text makra nevojde na jeden riadok, môžeme ho rozdeliť na viac nasledujúcich riadkov. Skutočnosť, že makro pokračuje na nasledujúcom riadku, sa určí umiestnením znaku \ ako posledného znaku na riadku.

#define DLHA_KONSTANTA Toto je dlha konstanta, ktora sa \
					 nevojde do jedneho riadku.

K tomuto všetkému však uveďme, že sme skôr popísali možnosť nie nástroj. Makrá majú zvýšiť čitateľnosť programu, nemajú za úlohu urobiť s programu ťažko zrozumiteľný rébus.


Diskusia a komentáre

Diskusia a komentáre