Kirjoittaja: Metabolix
Kirjoitettu: 13.09.2007 – 10.08.2013
Tagit: koodi näytille, vinkki
Ajoittain kysellään, kuinka voi jakaa ohjelmakoodinsa moneen tiedostoon tai tehdä oman kirjaston. Tämän vinkin on tarkoitus auttaa näissä asioissa. C++-ohjelmoijien kannattaa katsoa myös vastaava C++-tyylinen vinkki.
Kaikki tuntevat esikääntäjäkomennon #include
, mutta monelle lienee epäselvä sen todellinen merkitys. #include
liittää toisen tiedoston juuri siihen kohti lähdekoodia, kas tähän tapaan:
/* Olkoon tämä luvut.txt */ 1, 2, 3, 4
/* Olkoon tämä koodi.c ennen käsittelyä */ int luvut[] = { #include "luvut.txt" };
Kun koodi.c ajetaan esikääntäjän läpi, lopputuloksena syntyy tällainen:
int luvut[] = { 1, 2, 3, 4 };
Samalla tavalla toimii myös kirjastojen salaperäisten ".h-tiedostojen" eli otsikkotiedostojen liittäminen. Mutta kuinka yhdessä tai muutamassa lyhykäisessä otsikkotiedostossa voi olla niin mutkikkaita kirjastoja kuin vaikkapa SDL tai OpenGL?
Avainsana on extern
. Sen avulla kääntäjälle kerrotaan, että muuttuja tai funktio on ulkopuolinen, siis toisessa kooditiedostossa tai ehkä jopa jossakin aivan muualla. Valmiiksi käännettyjen kirjastojen tapauksessa funktiot sijaitsevat usein dynaamisesti linkitettävässä kirjastossa (DLL, SO tai vastaava), mutta näiden rakenteeseen en tässä puutu enempää. Omassa ohjelmassa funktio voi olla vaikkapa toisessa kooditiedostossa. Tuo toinenkin kooditiedosto täytyy tietenkin kääntää ja linkittää mukaan ohjelmaan. Yleensä IDEt, myös Dev-C++, hoitavat kaiken tämän, kun kooditiedosto vain on mukana samassa projektissa. Viimeisessä listauksessa esitellään vielä komennot GCC:n käyttäjiä varten.
Jos sama koodi tulee käännetyksi ja linkitetyksi moneen kertaan tai jos esimerkiksi samoja muuttujatyyppejä määritellään koodissa useampaan kertaan, aiheutuu yleensä ongelmia. Jokainen C- tai C++-tiedosto yleensä käsitellään vain kerran, ongelmaksi jäävät siis enää otsikkotiedostot: jotenkin pitää varmistaa, että saman otsikkotiedoston sisältöä ei liitetä ohjelmaan kuin kerran. Tässä auttavat mukavasti esikääntäjän makrot ja ehtolauseet. Oleelliset komennot ovat käyttöjärjestyksessä #ifndef
, #define
ja #endif
.
omajuttu.h
/* Varmistetaan, että ei ole vielä liitetty */ #ifndef OMAJUTTU_H /* Merkitään, että nyt on */ #define OMAJUTTU_H 1 /* Sitten otsikon sisältö, tällä kertaa tyyppimäärittely ja ulkoinen funktio sekä vakio */ struct juttu { int luku, toinen; }; typedef struct juttu juttu; extern int summaa(const juttu *j); extern const juttu vakiojuttu; /* Jos käyttää C:tä ja C++:aa sekaisin, pitää muistaa, että C++ sotkee funktioiden nimiä paluuarvon ja parametrien mukaan. Jos siis funktio sijaitsee oikeasti C-koodissa, pitää otsikkoon muistaa merkitä extern "C" pelkän externin sijaan. */ #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif EXTERN int c_nelio(int a); /* Ja lopuksi suljetaan ehto (#ifndef OMAJUTTU_H) */ #endif
omajuttu.c
/* Liitetään oma otsikko mukaan, jotta saadaan juttu-tyypin määrittely */ #include "omajuttu.h" /* Määritellään vakiojuttu: */ const juttu vakiojuttu = { 101, 1337 }; /* Sitten ne oikeat funktiot. Externiä ei tarvita uudestaan. */ int c_nelio(int a) { return a * a; } int summaa(const juttu *j) { return j->luku + j->toinen; }
ohjelma.c
/* Täälläkin tarvitaan omajuttu.h, jotta voidaan käyttää sen sisältämiä funktioita ja tyyppejä */ #include "omajuttu.h" /* Lisäksi tietenkin tavallisia otsikoita */ #include <stdio.h> int main(void) { /* käytetään ulkoista vakiota; muuttuja toimisi aivan samoin */ juttu j = vakiojuttu; printf("%d + %d = %d\n", j.luku, j.toinen, summaa(&j)); printf("c_nelio(%d) = %d\n", summaa(&vakiojuttu), c_nelio(summaa(&vakiojuttu))); return 0; }
Käännös komentorivillä GCC:llä
Windowsissa kannattaa varmaankin vaihtaa komennoista pääte .bin päätteeksi .exe.
# lyhyesti: gcc ohjelma.c omajuttu.c -o ohjelma.bin # osissa: gcc ohjelma.c -c -o ohjelma.o gcc omajuttu.c -c -o omajuttu.o gcc omajuttu.o ohjelma.o -o omajuttu.bin # Sen sijaan C++:n osittainen käyttö aiheuttaisi ongelman: gcc omajuttu.c -c -o omajuttu.o # C-käännös kirjastosta g++ ohjelma.c -c -o ohjelma.o # C++-käännös ohjelmasta g++ omajuttu.o ohjelma.o -o omajuttu.bin # Virhe: undefined reference to `summaa(juttu const*)' # Pitäisi siis muistaa 'extern "C"' eli EXTERN
// Lopputuloksena syntyy tällainen: int luvut[] = { 1, 2, 3, 4 };
Ei, vaan
// Lopputuloksena syntyy tällainen: int luvut[] = { // Olkoon tämä luvut.txt 1, 2, 3, 4 };
lainaus:
Ei, vaan
Kylläpäs. Poistaahan esikäsittely kommentitkin. Siirsin selkeyden vuoksi tuon toisenkin kommentin ulos kooditagista.
Kiits :)
Kiva goodivinkki.
Hyvää peruskauraa putkaan tämä. Samaan syssyyn voisi vaikka jatkaa vinkkiä toisella osalla vaikka headereiden kanssa?
No eikö tuossa muka headeriakin ole?
Heh, oot menny keksimään pyörän uudestaan :) no ei mitään, aloittelijalle hyvä opas :)
Jepjep:dd
kray kirjoitti:
Heh, oot menny keksimään pyörän uudestaan :) no ei mitään, aloittelijalle hyvä opas :)
Monien vinkkieni on tarkoituskin olla juuri aloittelijoille.
Haukkusin todella hyväksi esimerkiksi.
On se hyvä, että on näinkin hyviä esimerkkejä aloittelijoille. Internetistä löytyy paljon esimerkkejä tästä teoriassa ja melko ylimalkaisesti selitettynä, mutta tästä saa selvää helposti ja havainnollistaa hyvin staattista linkitystä.
Miten tämä onnistuu luokkien kanssa, jossa on jäsenfunktioita? Ja sitten on vielä muuttujia joita tarvitaan sekä Player.cpp että main.cpp?
Aivan samalla tavalla. Jos kirjoitat funktiot luokan ulkopuolelle (void Luokka::funktio() {}
), ne tulee sijoittaa kooditiedostoon, kuten esimerkin funktiot. Jos taas kirjoitat ne luokkamäärittelyn sisään, ne ovat ns. inline-funktioita, jotka siis käännetään koodin sekaan kutsuhetkellä, jolloin itse funktiota ei välttämättä ole olemassa. Pitkiä funktioita ei tulisikaan kirjoittaa määrittelyn sisään.
Kiitos tuosta tiedosta että siihen kohtaan liittääpi viittaan että tuollaisen numero jononkin voi liittää. Muuten tuttua asiaa aikas lailla vaikka unohtunutta tuo asia jota ei kirjallisuuteni kerro. ps. c rulettaa enempi kuin c++ no c++ tekee valtaisia ohjelmia makuuni ja en ole vielä nähny hyivä c++ kirjoja siedettävän pelastin kirjastosta siinä ei ole kaikkea.