Nimensä mukaisesti esiprosessorit ovat ohjelmia, jotka käsittelevät lähdekoodiamme ennen kääntämistä. Ohjelman kirjoittamisen ja ohjelman suorittamisen välillä on useita vaiheita C/C++:ssa. Katsotaanpa näitä vaiheita ennen kuin alamme varsinaisesti tutustua esikäsittelijöihin.
Yllä olevasta kaaviosta näet välivaiheet. Ohjelmoijan kirjoittama lähdekoodi tallennetaan tiedostoon program.c. Tämän jälkeen esiprosessorit käsittelevät tätä tiedostoa ja luodaan laajennettu lähdekooditiedosto nimeltä program. Kääntäjä kääntää tämän laajennetun tiedoston ja tuottaa objektikooditiedoston nimeltä program .obj. Lopuksi linkittäjä linkittää tämän objektikooditiedoston kirjaston funktioiden objektikoodiin, jolloin syntyy suoritettava tiedosto program.exe.
Preprosessoriohjelmat tarjoavat preprocessors-direktiivejä, jotka kertovat kääntäjälle lähdekoodin esikäsittelyn ennen kääntämistä. Kaikki nämä esikäsittelijän direktiivit alkavat ’#’-symbolilla (hash). ’#’-symboli osoittaa, että mikä tahansa lauseke, joka alkaa #:llä, on menossa esiprosessoriohjelmaan, ja esiprosessoriohjelma suorittaa tämän lausekkeen. Esimerkkejä joistakin esikäsittelijän direktiiveistä ovat: #include, #define, #ifndef jne. Muista, että #-symboli antaa vain polun, jonka kautta se menee esiprosessorille, ja esiprosessoriohjelma käsittelee komennon kuten include. Esimerkiksi include sisällyttää ylimääräistä koodia ohjelmaasi. Voimme sijoittaa nämä esikäsittelijän direktiivit minne tahansa ohjelmassamme.
Edeltäjädirektiivejä on 4 päätyyppiä:
- Makrot
- File Inclusion
- Conditional Compilation
- Other directives
Tutustutaan nyt kuhunkin näistä direktiiveistä tarkemmin.
- Makrot: Makrot ovat ohjelmassa oleva koodinpätkä, jolle annetaan jokin nimi. Aina kun kääntäjä kohtaa tämän nimen, kääntäjä korvaa nimen varsinaisella koodinpätkällä. Makron määrittelyyn käytetään ’#define’-direktiiviä. Ymmärtäkäämme nyt makron määrittelyä ohjelman avulla:
#include <iostream>
#define LIMIT 5
int
main()
{
for
(
int
i = 0; i < LIMIT; i++) {
std::cout << i <<
"\n"
;
}
return
0;
}
#include <stdio.h>
#define LIMIT 5
int
main()
{
for
(
int
i = 0; i < LIMIT; i++) {
printf
(
"%d \n"
,i);
}
return
0;
}
Output:
01234
Yllä olevassa ohjelmassa kääntäjän suorittaessa sanaa LIMIT se korvaa sen sanalla 5. Makromäärittelyssä olevaa sanaa ’LIMIT’ kutsutaan makromalliksi ja ’5’ on makrolaajennus.
Huomaa: Makromäärittelyn lopussa ei ole puolipistettä(’;’). Makromääritykset eivät tarvitse puolipistettä loppuun.
Makrot, joilla on argumentteja: Voimme myös välittää argumentteja makroihin. Argumenttien kanssa määritellyt makrot toimivat samalla tavalla kuin funktiot. Ymmärretään tämä ohjelman avulla:
#include <iostream>
#define AREA(l, b) (l * b)
int
main()
{
int
l1 = 10, l2 = 5, area;
area = AREA(l1, l2);
std::cout <<
"Area of rectangle is: "
<< area;
return
0;
}
#include <stdio.h>
#define AREA(l, b) (l * b)
int
main()
{
int
l1 = 10, l2 = 5, area;
area = AREA(l1, l2);
printf
(
"Area of rectangle is: %d"
, area);
return
0;
}
Output:
Area of rectangle is: 50
Yllä olevasta ohjelmasta nähdään, että aina kun kääntäjä löytää ohjelmasta AREA(l, b), se korvaa sen lausekkeella (l*b) . Tämän lisäksi myös makromallille AREA(l, b) annetut arvot korvataan lausekkeella (l*b). Näin ollen AREA(10, 5) on yhtä suuri kuin 10*5.
- Tiedoston sisällyttäminen: Tämän tyyppinen esikäsittelijädirektiivi käskee kääntäjää sisällyttämään tiedoston lähdekoodin ohjelmaan. On olemassa kahdenlaisia tiedostoja, jotka käyttäjä voi sisällyttää ohjelmaan:
- Otsikkotiedosto tai vakiotiedostot: Nämä tiedostot sisältävät ennalta määriteltyjen funktioiden, kuten printf(), scanf() jne. määrittelyn. Nämä tiedostot on sisällytettävä, jotta näiden funktioiden kanssa voidaan työskennellä. Eri funktiot ilmoitetaan eri otsikkotiedostoissa. Esimerkiksi tavalliset I/O-funktiot ovat tiedostossa ’iostream’, kun taas funktiot, jotka suorittavat merkkijono-operaatioita, ovat tiedostossa ’string’.
Syntaksi:
- Otsikkotiedosto tai vakiotiedostot: Nämä tiedostot sisältävät ennalta määriteltyjen funktioiden, kuten printf(), scanf() jne. määrittelyn. Nämä tiedostot on sisällytettävä, jotta näiden funktioiden kanssa voidaan työskennellä. Eri funktiot ilmoitetaan eri otsikkotiedostoissa. Esimerkiksi tavalliset I/O-funktiot ovat tiedostossa ’iostream’, kun taas funktiot, jotka suorittavat merkkijono-operaatioita, ovat tiedostossa ’string’.
#include< file_name >
jossa file_name on sisällytettävän tiedoston nimi. ’<’- ja ’>’-sulkeet kertovat kääntäjälle, että tiedosto etsitään standardihakemistosta.
- käyttäjän määrittelemät tiedostot: Kun ohjelmasta tulee hyvin suuri, on hyvä käytäntö jakaa se pienempiin tiedostoihin ja sisällyttää ne aina tarvittaessa. Tällaiset tiedostot ovat käyttäjän määrittelemiä tiedostoja. Nämä tiedostot voidaan sisällyttää seuraavasti:
#include"filename"
- Ehdollinen kääntäminen: Conditional Compilation -direktiivit ovat tyyppisiä direktiivejä, jotka auttavat kääntämään tietyn osan ohjelmasta tai ohittamaan jonkin tietyn ohjelman osan kääntämisen joidenkin ehtojen perusteella. Tämä voidaan tehdä kahden esikäsittelykomennon ’ifdef’ ja ’endif’ avulla.
Syntaksi:
#ifdef macro_name statement1; statement2; statement3; . . . statementN;#endif
Jos makro, jonka nimi on ’macroname’, on määritelty, lausekelohko suoritetaan normaalisti, mutta jos sitä ei ole määritelty, kääntäjä yksinkertaisesti ohittaa tämän lausekelohkon.
- Muut direktiivit: Edellä mainittujen direktiivien lisäksi on kaksi muuta direktiiviä, joita ei yleisesti käytetä. Nämä ovat:
- #undef-direktiivi: #undef-direktiiviä käytetään olemassa olevan makron peruuttamiseen. Tämä direktiivi toimii seuraavasti:
#undef LIMIT
Tämän lausekkeen käyttäminen kumoaa olemassa olevan makron LIMIT. Tämän lausekkeen jälkeen jokainen ”#ifdef LIMIT”-lauseke on false.
- #pragma-direktiivi: Tämä direktiivi on erikoisdirektiivi, ja sitä käytetään joidenkin ominaisuuksien kytkemiseen päälle tai pois päältä. Tämäntyyppiset direktiivit ovat kääntäjäkohtaisia, eli ne vaihtelevat kääntäjittäin. Joitakin #pragma-direktiivejä käsitellään seuraavassa:
- #pragma startup ja #pragma exit: Nämä direktiivit auttavat meitä määrittelemään funktiot, jotka on suoritettava ennen ohjelman käynnistystä( ennen kuin ohjaus siirtyy main()) ja juuri ennen ohjelman poistumista (juuri ennen kuin ohjaus palaa main():sta).
Huomautus: Alla oleva ohjelma ei toimi GCC-kääntäjillä.
Katsokaa alla olevaa ohjelmaa:
- #pragma startup ja #pragma exit: Nämä direktiivit auttavat meitä määrittelemään funktiot, jotka on suoritettava ennen ohjelman käynnistystä( ennen kuin ohjaus siirtyy main()) ja juuri ennen ohjelman poistumista (juuri ennen kuin ohjaus palaa main():sta).
#include <bits/stdc++.h>
using
namespace
std;
void
func1();
void
func2();
#pragma startup func1
#pragma exit func2
void
func1()
{
cout <<
"Inside func1()\n"
;
}
void
func2()
{
cout <<
"Inside func2()\n"
;
}
int
main()
{
void
func1();
void
func2();
cout <<
"Inside main()\n"
;
return
0;
}
#include <stdio.h>
void
func1();
void
func2();
#pragma startup func1
#pragma exit func2
void
func1()
{
printf
(
"Inside func1()\n"
);
}
void
func2()
{
printf
(
"Inside func2()\n"
);
}
int
main()
{
void
func1();
void
func2();
Inside func1()Inside main()Inside func2()
Yllä oleva koodi tuottaa alla olevan tulosteen, kun se ajetaan GCC-kääntäjillä:
Inside main()
Tämä tapahtuu, koska GCC ei tue #pragma startup tai exit. Voit kuitenkin käyttää alla olevaa koodia saadaksesi samanlaisen tulosteen GCC-kääntäjillä.
#include <stdio.h>
void
func1();
void
func2();
void
__attribute__((constructor)) func1();
void
__attribute__((destructor)) func2();
void
func1()
{
printf
(
"Inside func1()\n"
);
}
void
func2()
{
printf
(
"Inside func2()\n"
);
}
int
main()
{
printf
(
"Inside main()\n"
);
Tätä direktiiviä käytetään kääntämisen aikana näytettävien varoitusviestien piilottamiseen. Voidaan piilottaa varoitukset alla esitetyllä tavalla:
- #pragma warn -rvl: Tämä direktiivi piilottaa ne varoitukset, jotka annetaan, kun funktio, jonka pitäisi palauttaa arvo, ei palauta arvoa.
- #pragma warn -par: Tämä direktiivi piilottaa ne varoitukset, jotka annetaan, kun funktio ei käytä sille välitettyjä parametreja.
- #pragma warn -rch: Tämä direktiivi piilottaa ne varoitukset, jotka annetaan, kun koodi ei ole tavoitettavissa. Esimerkiksi: mikä tahansa koodi, joka on kirjoitettu funktiossa return-lausekkeen jälkeen, on saavuttamatonta.
Tämän artikkelin on kirjoittanut Harsh Agarwal. Jos pidät GeeksforGeeksistä ja haluat osallistua, voit myös kirjoittaa artikkelin osoitteessa contribute.geeksforgeeks.org tai lähettää artikkelisi sähköpostitse osoitteeseen [email protected]. Näet artikkelisi näkyvän GeeksforGeeksin pääsivulla ja voit auttaa muita nörttejä.