C/Otsikkotiedostot

< C

Tämä luku kertoo C:n otsikkotiedostoista.

Pienet ohjelmat saattavat hyvin mahtua yhteen lähdekooditiedostoon. Suurempia ohjelmia tai kirjastoja varten tarvitsee kuitenkin useamman lähdekooditiedoston. Tällöin kuitenkin törmää nopeasti ongelmaan.

Kuvitellaan, että meillä on kaksi lähdekooditiedostoa:

laskin.c
 /* Laskee ympyrän pinta-alan säteestä. */
 float ympyran_pinta_ala(float r) {
   return r * r * 3.14159f;
 }
main.c
 #include <stdio.h>
 #include <stdlib.h>
 int main(int argc, char* argv[]) {
   float r;
   puts("Syötä ympyrän säde");
   if (!scanf("%f", &r)) {
     puts("Sädettä ei voitu lukea");
   } else {
     /* laske pinta-ala säteestä */
     float a = ympyran_pinta_ala(r);
     printf("Ympyrän pinta-ala on %f\n", a);
   }
   return EXIT_SUCCESS;
 }

Tämä ohjelma ei käänny, koska kääntäjä ei main.c-tiedostoa kääntäessään tiedä mitään laskin.c-tiedoston ympyran_pinta_ala-funktiosta. Yksi ratkaisu olisi kirjoittaa main.c:hen #include "laskin.c", mutta tämä kopioi koko tiedoston sisällön main.c:hen, mikä ei ole hyvä ratkaisu (main.c pitää kääntää uudelleen vaikka vain laskin.c muuttuisi, ja koodin koko kasvaa).

Muuttujat- ja Funktiot-luvuissa käsiteltiin extern-määrettä. Sillä voi esitellä funktioita tai muuttujia, jotka määritellään toisessa tiedostossa. Funktiota esiteltäessä extern-määrettä ei tarvitse erikseen kirjoittaa. Parempi ratkaisu olisikin siis #include "laskin.c":n sijasta kirjoittaa ennen main-funktiota

 float ympyran_pinta_ala(float r);

Tällöin, kun lähdekooditiedostot linkataan yhteen ohjelmaan, se toimii oikein.

Tässäkin ratkaisussa on ongelma. Entä jos lisäämme laskin.c:hen lisää funktioita? Silloin main.c:hen pitää lisätä lisää samanlaisia esittelyitä. Tästä tulee äkkiä ongelma, jos laskin.c:n funktioita tarvitaan useasta lähdekooditiedostosta.

Ratkaisu ovat otsikkotiedostot (engl. header files). Niiden tiedostopääte on tavallisesti .h, kun lähdekooditiedostojen on .c. Otsikkotiedostojen tarkoituksena on sisältää esittelyjä ja tyyppimäärityksiä, joista on hyötyä muissa lähdekooditiedostoissa.

Jos tekee omia otsikkotiedostoja, on tärkeää, että niihin toteuttaa esikääntäjällä toiminnon, joka estää otsikkotiedoston sisältöä sisältymästä useampaan kertaan:

 #ifndef OMA_OTSIKKO_H
 #define OMA_OTSIKKO_H
 /* kaikki tyyppimäärittelyt ja esittelyt tänne sisään */
 #endif

Voisimme siis luoda uuden tiedoston, jossa on seuraava sisältö:

laskin.h
 #ifndef LASKIN_H
 #define LASKIN_H
 float ympyran_pinta_ala(float r);
 #endif

Yleisesti ottaen otsikkotiedostolle kannattaa antaa sama nimi (tiedostopäätettä lukuun ottamatta) kuin sille lähdekooditiedostolle, jonka toteutuksia se kuvailee.

Nyt voimme kirjoittaa main.c:hen (ennen main-funktiota; otsikkotiedostojen sisällytys kannattaa olla aina ensimmäinen asia lähdekooditiedostossa mahdollisten kommenttien jälkeen)

 #include "laskin.h"

niin nyt ympyrän pinta-alan laskeva funktio on helposti saatavilla. main.c pitää nyt kääntää uudelleen vain, jos sen oma sisältö muuttuu, tai jos sen käyttämien otsikkotiedostojen sisältö muuttuu. Jos ainoastaan funktion toteutus lähdekooditiedostossa laskin.c muuttuu, main.c:tä ei tarvitse kääntää uudelleen.

Kun lähdekooditiedosto (teknisesti ottaen käännösyksikkö, engl. compilation unit) käännetään, saadaan tulokseksi objektitiedosto (engl. object file). Nämä objektitiedostot voidaan sitten linkittää yhteen suoritettavaan ohjelmaan tai vaikka kirjastoon, jota muut ohjelmat voivat käyttää. Otsikkotiedostoja ei käännetä erikseen. Esimerkiksi gcc-kääntäjällä komennot tämän ohjelman koontamiseksi (engl. build) voisivat näyttää tältä:

 gcc -c -o laskin.o laskin.c            # kääntää tiedoston laskin.c objektitiedostoksi laskin.o
 gcc -c -o main.o main.c                # kääntää tiedoston main.c objektitiedostoksi main.o
 gcc -o ohjelma laskin.o main.o         # linkittää objektitiedostot laskin.o ja main.o suoritettavaksi ohjelmaksi nimeltään ohjelma

Kun useampi lähdekooditiedosto linkitetään yhteen, linkitysohjelma vaatii, että jokainen funktio ja muuttuja toteutetaan (määritellään) vain kerran, ellei määritelmän näkyvyyttä ole rajoitettu saman lähdekoodin sisälle static-määreellä. Esimerkiksi yllä mainittu huono ratkaisu, jossa main.c:hen lisättiin #include "laskin.c", ei toimisi: ympyran_pinta_ala on määritelty tiedostossa laskin.c, mutta koska sen sisältö kopioidaan myös main.c:hen, myös siinä tiedostossa.

Kun lähdekoodi- ja otsikkotiedostoja on monta, käännöskomentojen suuri määrä ja se, milloin ne oikein pitääkään taas suorittaa, alkaa puuduttaa. Tämän takia C-ohjelmat käyttävät usein jonkinlaista koontijärjestelmää (engl. build system). Yleinen vaihtoehto on esim. GNU Make, joka käyttää Makefile-nimistä skriptiä käännöskomentojen määrittämiseen. Koontijärjestelmiä ei käsitellä tässä kirjassa sen koommin.

Omien tietotyyppien, kuten tietueiden, esittelyt ja määrittelyt kuuluvat myös otsikkotiedostoihin. Tyypin voi pelkästään esitellä:

 struct oma_tietotyyppi;

mutta jos lähdekooditiedosto ei pääse käsiksi tietotyypin määrittelyyn, se on vaillinainen (engl. incomplete). Vaillinaisen tyypin saa mainita vain osoittimissa, eikä sen kenttiin voi viitata, koska kääntäjä ei lähdekooditiedostoa kääntäessään tiedä, missä jokin kenttä olisi tietueessa. Jos siis lähdekooditiedoston pitäisi pystyä pääsemään tietueen kenttiin tai määrittämään sentyyppisiä muuttujia (ei vain osoittimia sentyyppisiin tietueisiin), tyyppi täytyy määritellä otsikkotiedostossa.

C-ohjelmointikieli

EtusivuHistoriaTyökalut


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


C-kielen varatut sanatStandardikirjastoAiheesta muualla