Esikääntäjä¶
C-koodissa olemme jo käyttäneet mystisiä
#
-alkuisia käskyjä. Nämä ovat esikääntäjän käskyjä, joilla voidaan esitellä ohjelmaan vakioita ja makroja, tuoda koodiin ulkopuolisia kirjastoja ja ohjata varsinaisen C-kääntäjän käännösprosessia.Kirjastot mukaan¶
Ulkopuolista ohjelmakoodia voidaan esitellä koodissamme
#include
-käskyllä, joka syntaksi on seuraava:#include <otsikkotiedosto.h>
#include "otsikkotiedosto.h"
Otsikkotiedostot ovat kirjastojen funktioiden esittelytiedostoja, joita käsittelemme tarkemman kirjastojen yhteydessä.
Jos includen yhteydessä käytetään
< >
-merkkejä, hakee kääntäjä otsikkotiedostoa kääntäjän tiedostoista esim. standardikirjastosta. Jos taas käytetään " "
-lainausmerkkejä, etsitään otsikkotiedostoa samasta hakemistorakenteesta missä ohjelmakoodimme sijaitsee. Eli ajatuksena on, että ohjelmoijan omiin kirjastoihin tai ohjelmaan ulkoa tuotuihin kirjastoihin käytettäisiin lainausmerkkejä. Esimerkiksi standardikirjaston jo tutuille tulostusfunktioille tiedämme löytyvän otsikkotiedoston
stdio.h
, joka tuodaan koodiin käskyllä:#include <stdio.h>
}}}.
== Vakiot ==
Vakioita on kätevä käyttää jonkin usein esiintyvän, esimerkiksi numeerisen vakioarvon, käyttämiseen koodissa. Vakion arvo voi olla mitä tahansa kunhan se noudattaa C-kielen syntaksia.
(Esikääntäjä)vakioita ei ole olemassa kuin esikäännettävässä koodissa, koska '''esikääntäjä korvaa koodista''' vakion sen arvolla literaalisesti yksi yhteen. Eli ''vakiolla ei ole muistipaikkaa''. Tästä tietenkin seuraa, ettei ohjelmakoodissa vakioon voi tallettaa arvoa, tai käyttää sitä muuttujan tavoin. Voisimme tietenkin esitellä vakioita myös muuttujilla, mutta tällöin ne varaavat muistia. Jos vakion arvoa halutaan muuttaa, riittää että muutetaan vain koodin alusta vakion arvoa, ihan kuten muuttujissakin.
Esikääntäjän {{{#define}}}-käskyllä voimme esitellä koodissamme vakiota. Syntaksi on seuraava:
{{{highlight=c
#define VAKION_NIMI arvo // Tyylillisesti vakiot yleensä kirjoitetaan isoilla kirjaimilla.
Esimerkki.
#define PI 3.14159
float keha = 2 * PI * sade;
Koodi kääntyisi esikääntäjässä seuraavasti:
float keha = 2 * 3.14159 * sade;
Eli vakiolle ei tehdä mitään muunnoksia, sen arvo pistetään koodin sellaisenaan. Tässä on kääntäjän toiminnassa poikkeuksia, vakiota ei voi laittaa lainausmerkkien sisään eikä vakiona pidetä sanan osaa.
#define TRUE 1
...
printf("TRUE"); // tätä ei käsitetä vakiona, kokeile!
int x = TRUETRUE; // tätä ei käsitetä yhtenä eikä kahtena vakiona
enum-muuttujatyyppi¶
Vakio-arvojen esittelyyn on myös toinen tapa, nimittäin
enum
-muuttujatyyppi.enum kukat { ORVOKKI=1, RUUSU, AURINGONKUKKA };
Nyt enum:n arvot asetetaan kasvavasti alkaen ensimmäisen vakion alustusarvosta. Jos alustusarvoa ei ole, lähdetään nollasta. enum-vakioita voidaan käyttää kuten #define-vakioitakin, mutta erona on että niitä voidaan tarkastella koodissa, toisin kuin esikääntäjässä katoavat #define-vakiot.
kukat kukka = RUUSU; // Uusi muuttujatyyppi kukat!
Luonnollisesti, voimme tehdä totuusarvot ohjelmaamme seuraavasti
enum
:n avulla.enum boolean { FALSE=0, TRUE };
...
boolean onko_totta = FALSE;
Makrot¶
Vakiolla voidaan myös korvata usein käytettävää koodia, jolloin puhutaan makroista. Makroille voidaan myös antaa argumentteja.
#define FOREVER for(;;)
#define INC(A) A++;
Koodissa esikääntäjä korvaa makrot ihan vastaavasti kuin vakiot.
Jälkimmäisessä
INC
-vakiossa annamme makrolle parametriksi muuttujan, jonka arvoa se kasvattaa yhdellä. Nyt ohjelmoijan täytyy itse huolehtia että makroa käytetään oikein, huomioidaan muuttujatyypit jne. Tämä voi helposti johtaa hyönteisiin koodissa, jos esimerkiksi emme ympäröi makroa sulkeilla, jolloin suoritusjärjestys voi olla jotai muuta kun mitä ajateltiin. Tai vakion määrittelyssä sen perään laitetaan ;
käskyn merkiksi, jolloin emme voi käyttää INC
-makroa osana esimerkiksi for-lausetta.#define INC(A) A++;
for (x=0; x < 100; INC(x)) {}
// vakio korvautuisi täsmälleen sen arvolla, jolloin
// sijoituslauseen x++ perässä on nyt ylimääräinen puolipiste.
for (x=0; x < 100; x++;) {}
Jos jätämme vakion määrittelystä puolipisteen pois, sitä voidaan käyttää koodissa muualla lisäämällä puolipiste perään
INC(x);
.Ehdollinen kääntäminen¶
Voimme esitellä vakion myös ilman arvoa. Tällöin vakio on voimassa, mutta sitä ei voi käyttää koodissa esikääntäjän ulkopuolella.
#define _WIN32
#define _USE_MATH_DEFINES
Tätä voidaan tyypillisaesti käyttää ohjaamaan kääntäjän toimintaa, eli mitä kirjastoja tai kirjastojen osia otetaan ohjelmaamme mukaan. Usein portattavan (ts. useissa eri ympäristöissä toimivan) koodin kääntämisen apuna on näitä käyttöjärjestelmä-spesifisia vakioita. Ylläoleva vakio esimerkiksi kertoo, että haluamme kääntää koodimme Windows-koneessa. Kääntäjäympäristöt yleensä automaattisesti asettavat nämä vakiot kohdalleen, mutta saatamme itse joutua asettamaan joitain niistä, kuten ylläolevan math.h-kirjaston vakion.
Eri käyttöjärjestelmästä meillä voi olla eri kirjastot näytölle piirtämiseen, vaikka ohjelma muuten toimisikin samoin. Tällöin puhutaan ehdollisesta mukaan ottamisesta. Usein tällainen vakio asetetaan kääntäjässä, ennenkuin se alkaa kääntämään koodia. Käyttämässämme gcc-kääntäjässä tätä varten on mm. komentoriviparametri. Esimerkki.
#ifdef __unix__ // Testaa onko vakio __unix__ asetettu
#include <unix.h>
#elif defined _WIN32 // Testaa onko vakio _WIN32 asetettu
#include <windows.h>
#endif
#if VERSION < 190
#error versions below 1.9.0 not supported
#endif
Esikääntäjässä on siis myös tuttu if-else if-rakenne toteutettuna. Ehtolauseke pitää lopettaa #endif-käskyllä.
Ehtoja joudumme käyttämään kirjastojen yhteydessä varmistamaan että otsikkotiedoston koodi käännetään vain kerran. Jos kirjastoa halutaan käyttää useissa eri koodimoduleissa, lisäämme siis sitä vastaavan #include-käskyn jokaiseen moduliin. Nyt kuitenkin kääntäjä kääntäisi koodin uudelleen jokaisen moduulin käännösvaiheessa. Tästähän kääntäjä ei tykkää, koska jo toisella käännöskerralla koodi on jo olemassa ja käännös päättyy virheilmoitukseen.
Tämän ongelman kierrämme esikääntäjän ehtolauseella seuraavasti, testaamalla vakion olemassa otsikkotiedoston ihan alussa.
#ifndef _HDR // Tarkista onko vakio _HDR asetettu
#define _HDR // Aseta vakio _HDR
...
// Otsikkotiedoston esittelyt
...
#endif
Tässä ohjaamme kääntäjää siis asettamalla ensimmäisellä käännöskerralla vakion
_HDR
, jonka olemassaolo estää uudelleenkäännökset.Esimerkki¶
Sulautetuissa järjestelmissä ehdollista kääntämistä käytetään saman koodin kääntämiseksi eri mikrokontrollereille. Luodaan alustakohtaiset vakiot ja esikääntäjällä ohjataan, että haluttu vakio tulee halutussa alustassa käyttöön.
Alla luodaan vakio, jolla tarkistaan onko nappia 1 painettu riippumatta siitä mikä MCU meillä on (joko ATmega128 tai ATtiny13). Huomataan, että eri MCU:ssa painonappi on kytketty eri I/O-porttiin, joille on laitteen kirjastoissa määritelty omat vakiot PINA, PAO, PINB, ja PB2.
#ifdef __AVR_ATmega128__
#define BUTTON1_DOWN (!(PINA & (1 << PA0)))
#else ifdef __AVR_ATtiny13__
#define BUTTON1_DOWN (!(PINB & (1 << PB2)))
#endif
Nyt siis ehdollisella kääntämisellä mahdollistetaan saman koodin toiminta eri hardiksella.
Lopuksi¶
Esikääntäjämakroihin liittyy paljonkin asioita, joita emme tässä käyneet läpi. Kurssilla ei ole tarpeen määritellä omia makroja, mutta käyttämämme laiteläheiset kirjastot niitä ovat meille turvallisesti saattaneet määritellä.
Anna palautetta
Kommentteja materiaalista?