C/Esikääntäjä

< C

Tämä luku kertoo C:n esikääntäjästä.

Ennen kuin ohjelman lähdekoodi käännetään, esikääntäjä (engl. preprocessor) käy sen läpi ja tekee tarvittavat muutokset. Esikääntäjä on nimestään huolimatta osa C-kääntäjää, ja standardi asettaa vaatimuksia esikääntäjän toiminnoille.

Esikääntäjän tärkeimpiä toimintoja ovat makrot. Ne ovat nimiä, jotka korvataan automaattisesti toisella tekstillä. Makron voi määritellä esikääntäjän komennolla #define (kaikki esikääntäjän komennot eli direktiivit (engl. directive) alkavat merkillä #, ja niiden pitää olla omalla rivillään):

 #define PII 3.1415926535
 printf("piin likiarvo on %f\n", PII);
 /* esikäännetty rivi: printf("piin likiarvo on %f\n", 3.1415926535); */

Makron voi määritellä myös tyhjänä, jolloin esikääntäjä kuitenkin muistaa, että makro on sentään määritelty.

 #define OLEN_LUKENUT_MAKRON

Makroilla voi myös olla parametreja:

 #define MIN(x, y) ((x) < (y) ? (x) : (y))
 printf("luku 1 on %d ja luku 2 on %d; niistä pienempi on %d\n", 3, 5, MIN(3, 5));
 /* esikäännetty rivi: printf("luku 1 on %d ja luku 2 on %d; niistä pienempi on %d\n", 3, 5, ((3) < (5) ? (3) : (5))); */

Huomaa sulkeet. Esikääntäjä teeskentelee tietämätöntä kielen syntaksista ja laskujärjestyksestä, joten runsas sulkeiden käyttö on suotavaa:

 #define KERTOLASKU(x, y) x * y
 KERTOLASKU(1 + 2, 3 + 4)
 /* esikäännetty rivi: 1 + 2 * 3 + 4, joka tulkitaan 1 + (2 * 3) + 4, *EI* (1 + 2) * (3 + 4)! */
 #define KERTOLASKU(x, y) (x) * (y)
 KERTOLASKU(2, 7) / KERTOLASKU(1, 5)
 /* esikäännetty rivi: (2) * (7) / (1) * (5), joka *EI* ole sama kuin (2 * 7) / (1 * 5). tarvitaan vielä yhdet sulkeet! */

Tämän lisäksi pitää muistaa, että esikääntäjän makrot eivät ole funktioita:

 #define MIN(x, y) ((x) < (y) ? (x) : (y))
 MIN(a(), b())
 /* esikäännetty rivi: ((a()) < (b()) ? (a()) : (b())). huomaa, että funktioita kutsutaan kahdesti. */

Makroissa voi käyttää erikoismerkkejä # ja ##. Jos # lisätään makron parametrin eteen, esikääntäjä tekee siitä merkkijonoliteraalin. ## osien välissä yhdistelee sanueita eli tokeneita.

 #define TULOSTA_MINIMIARVO(x) #x, x##_MIN
 printf("tyypin %s minimiarvo on %d\n", TULOSTA_MINIMIARVO(INT));
 /* esikäännetty rivi: printf("tyypin %s minimiarvo on %d\n", "INT", INT_MIN); */

Makron voi poistaa #undef-komennolla.

Lisäksi esikääntäjällä voi ehtojen mukaan jättää tiettyä osaa koodista kääntämättä.

#if aloittaa tällaisen ehdollisen lohkon. Jos sen perässä oleva ehto ei ole tosi, lohkossa olevaa koodia ei käännetä. #elif vastaa else if:iä, #else else:iä ja #endif päättää lohkon.

 #if 2 + 3 == 5
   puts("Matematiikan säännöt toimivat oikein");
 #else
   puts("Matematiikan säännöt ovat muuttuneet yön aikana");
 #endif

Yllä olevassa esimerkissä vain ensimmäinen puts käännetään; toinen poistetaan koodista esikäännöksen aikana.

Ehdoissa voi käyttää erityistä, vain niissä käytettävää kielen sisäänrakennettua, makroa defined, joka tarkistaa, onko sennimistä makroa määritelty: #if defined(MAKRO). Tämän ehdon voi kääntää normaalisti operaattorilla: #if !defined(MAKRO). Nämä kaksi voidaan myös esittää lyhyemmin: #ifdef MAKRO, #ifndef MAKRO. #elif:ssä pitää kuitenkin käyttää pidempää muotoa.

Esikääntäjän ehtojen on oltava sellaisia, että esikääntäjä yksin tietää niistä. Niissä ei siis voi käyttää kuin vain makroja, ei esim. vakioita (const), saati sitten muuttujia. Jokseenkin ärsyttävää on se, ettei niissä voi käyttää sizeof-operaattoria. Esikääntäjät eivät yleensä kykene laskemaan liukuluvuilla, ainoastaan kokonaisluvuilla, eikä niissä voi käyttää ollenkaan osoittimia.

#include on esikääntäjän komento, joka sisällyttää toisen tiedoston osaksi nykyistä koodia. Näin tehdään yleensä otsikkotiedostojen, eli tiedostojen, joissa on esittelyjä ja tyyppimäärittelyjä, kanssa.

 #include <stdlib.h>        /* standardikirjasto */
 #include <SDL.h>           /* ulkoinen kirjasto */
 #include "otsikko.h"       /* oma otsikkotiedosto */

Lainausmerkeillä tiedostoa haetaan ensin lähdekooditiedostosta tai projektista. Jos sitä ei löydy, sitä haetaan sen jälkeen muualta, kuten järjestelmän tai kääntäjän tarjoamista tiedostoista. Kun käytetään kulmasulkeita, tiedostoa haetaan vain jälkimmäisistä. Yleensä kannattaakin siis käyttää kulmasulkeita standardikirjaston otsikoiden ja ulkoisten kirjaston kanssa, ja lainausmerkkejä ohjelman omien otsikkotiedostojen kanssa.

Tämän lisäksi on vielä #error-komento, joka aiheuttaa käännösaikaisen virheen. Sen perään pitää kirjoittaa viesti (joka kannattaa laittaa lainausmerkkeihin, jos siinä on erikoismerkkejä). #error:ia voi käyttää #if-komentojen ym. sisällä niin, että virhe tapahtuu vain, jos jokin ehto on tosi.

 #include <limits.h>
 #if CHAR_BIT != 8
 #error "this program requires a system with 8-bit bytes"
 #endif

Esikääntäjä määrittelee joitain omia makrojaan:

  • __STDC__, joka korvataan 1:llä: näin esikääntäjä ilmaisee, että se tukee C-standardia
  • [C99] __STDC_VERSION__, joka korvataan C-standardin versiolla: C99-standardilla 199901L, uudemmilla standardeilla jokin sitäkin korkeampi arvo.
    • Huomaa, että jos makroa ei ole määritelty, eli kyseessä on vanhempi standardi kuin C99, makroa voi silti käyttää vertailuissa. Jos määrittelemätöntä makroa käyttää esikääntäjäkomennon ehdossa tai vertailussa, sen arvoksi oletetaan 0. Näin voi esim. kirjoittaa #if __STDC_VERSION__ >= 199901L, ja tämä toimii aivan oikein.
  • __FILE__, joka korvataan tiedoston nimen tai polun sisältävällä merkkijonoliteraalilla
  • __LINE__, joka korvataan rivinnumerolla
  • __DATE__, joka korvataan merkkijonolla, joka ilmaisee käännöksen päivämäärää
  • __TIME__, joka korvataan merkkijonolla, joka ilmaisee käännöksen kellonaikaa

Makrotekniikoita

muokkaa
  • Lokimakro, joka automaattisesti täydentää lähdekooditiedoston nimen ja rivinnumeron:
#define LOG(args) (printf("[%s:%d] ", __FILE__, __LINE__), printf args, putchar('\n'))
LOG(("Tämä on testi %d", 123));
(printf("[%s:%d] ", __FILE__, __LINE__), printf("Tämä on testi %d", 123), putchar('\n'));
→ esim. [testi.c:4] Tämä on testi 123
    • C99-versiossa tämä on siistimpi, koska makrot voivat ottaa mukautuvan määrän parametreja:
    #define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
    LOG("Tämä on testi %d", 123);
    printf("[%s:%d] " "Tämä on testi %d", 123);
    → esim. [testi.c:4] Tämä on testi 123
  • "X-makro", jolla voi helposti toistaa samaa luetteloa eri tarkoituksissa (alla olevassa esimerkissä käytetään kenoviivaa, jolla voi jatkaa 'riviä' seuraavalle riville kooditiedostossa):
#define TIETUEEN_KENTAT                              \
    TIETUEEN_KENTTA(nimi,  const char *, "%s")       \
    TIETUEEN_KENTTA(lkm,   int,          "%d")       \
    TIETUEEN_KENTTA(hinta, float,        "%0.2f eur")

struct tuote {
#define TIETUEEN_KENTTA(NIMI, TYYPPI, KAAVA) TYYPPI NIMI;
    TIETUEEN_KENTAT
#undef TIETUEEN_KENTTA
};

void tulosta_tuotteen_tiedot(const struct tuote *tuote) {
#define TIETUEEN_KENTTA(NIMI, TYYPPI, KAAVA) printf("Tuotteen " #NIMI ": " KAAVA "\n", tuote->NIMI);
    TIETUEEN_KENTAT
#undef TIETUEEN_KENTTA
}
Tässä esimerkissä esikäännetty koodi näyttää seuraavalta (huomaa merkkijonoliteraalit; jos kirjoittaa monta merkkijonoliteraalia peräkkäin ilman operaattoria, kääntäjä yhdistää ne automaattisesti yhdeksi literaaliksi):
struct tuote {
    const char * nimi;
    int lkm;
    float hinta;
};

void tulosta_tuotteen_tiedot(const struct tuote *tuote) {
    printf("Tuotteen " "nimi" ": " "%s" "\n", tuote->nimi);
    printf("Tuotteen " "lkm" ": " "%d" "\n", tuote->lkm);
    printf("Tuotteen " "hinta" ": " "%0.2f eur" "\n", tuote->hinta);
}
C-ohjelmointikieli

EtusivuHistoriaTyökalut


Yksinkertainen C-kielinen ohjelmaMuuttujatOperaattoritKommentitOhjausrakenteetFunktiotOsoittimetDynaaminen muistinvarausTaulukotMerkkijonotTietueetEsikääntäjäOtsikkotiedostot


C-kielen varatut sanatStandardikirjastoAiheesta muualla