Kirjautuminen

Haku

Tehtävät

Koodivinkit: C: Koodin jakaminen eri tiedostoihin

Kirjoittaja: Metabolix

Kirjoitettu: 13.09.2007 – 10.08.2013

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

Kommentit

Jaska [15.09.2007 15:13:06]

Lainaa #

// 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
};

Metabolix [15.09.2007 15:17:27]

Lainaa #

lainaus:

Ei, vaan

Kylläpäs. Poistaahan esikäsittely kommentitkin. Siirsin selkeyden vuoksi tuon toisenkin kommentin ulos kooditagista.

moptim [15.09.2007 15:40:34]

Lainaa #

Kiits :)

Kiva goodivinkki.

E.K.Virtanen [17.09.2007 15:02:14]

Lainaa #

Hyvää peruskauraa putkaan tämä. Samaan syssyyn voisi vaikka jatkaa vinkkiä toisella osalla vaikka headereiden kanssa?

Metabolix [17.09.2007 19:11:49]

Lainaa #

No eikö tuossa muka headeriakin ole?

Kray [23.09.2007 12:22:35]

Lainaa #

Heh, oot menny keksimään pyörän uudestaan :) no ei mitään, aloittelijalle hyvä opas :)

Mega-Ohjelmoija [24.09.2007 15:54:32]

Lainaa #

Jepjep:dd

Metabolix [08.01.2008 17:45:48]

Lainaa #

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.

Cc [09.01.2008 17:37:44]

Lainaa #

Haukkusin todella hyväksi esimerkiksi.

GoldenDragon [16.10.2008 16:26:44]

Lainaa #

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ä.

vehkis91 [24.10.2008 13:33:52]

Lainaa #

Miten tämä onnistuu luokkien kanssa, jossa on jäsenfunktioita? Ja sitten on vielä muuttujia joita tarvitaan sekä Player.cpp että main.cpp?

Metabolix [24.10.2008 16:25:21]

Lainaa #

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.

JPQ [25.10.2009 05:30:39]

Lainaa #

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.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta