Kirjastot¶
Kirjastona ajatellaan mitä tahansa koodimodulin ulkopuolista C-koodia, jota haluamme uudelleenkäyttää. Tyypillisesti kirjastot sisältävät monikäyttöisiä funktioita, esim. matemaattisia funktioita, joille on useita eri käyttötapauksia Varsinainen kirjaston koodi voi olla C-kielinen ohjelma, ts. lähdekooditiedosto
pasta.c
, tai se voi olla valmiiksi käännettyä koodia (ts. binääri pasta.o
), joka linkitetään ohjelmaamme käännösprosessin aikana. Kirjaston esittely¶
Kirjastolla on aina oma otsikkotiedosto. Otsikkotiedostossa esitellään kirjaston ne funktiot, globaalit muuttujat ja vakiot (ja makrot), joita kirjastoa käyttävät ohjelmat saavat käyttöönsä. Otsikkotiedoston lisääminen kääntäjän näkökulmasta on sama kuin että otsikkotiedoston koodit kopioisi suoraan #include -käskyn tilalle kooditiedostoon.
Otsikkotiedosto
pasta.h
voisi olla esimerkiksi seuraava:#pragma once // Hox! Vakiolla estetään uudelleenkäännös
#include <stdio.h>
// Funktioiden prototyypit
uint8_t lisaa_vesi(void);
uint8_t lisaa_suola(void);
uint8_t kiehuuko_vesi(void);
void hammenna_kattilaa(void);
void aseta_keittoaika(void);
uint8_t onko_valmista(void);
// Uusi muuttujatyyppi vakioille, jännää!!
enum pasta_types { SPAGETTI=1, MAKARONI, FUSILLI, PENNE_RIGATE };
// Globaalit muuttujat
uint8_t _annoskoko; // Tämä muuttuja alustetaan pääohjelmassa
uint8_t _valittu_pasta = SPAGETTI;
Muuttujat otsikkotiedostossa¶
Muuttujien esittely ja/tai globaalien muuttujien käyttäminen suoraan otsikkotiedostojen kautta on hieman kyseenalaista, parempaa koodia olisi tehdä erilliset asetus- ja lukufunktiot tällaisille muuttujille. Näin muuttuja itse olisi kapseloituna kirjastomoduulin sisäiseksi muuttujaksi, jolloin muuttujan käyttö muissa moduleissa pysyy paremmin hanskassa.
void set_annoskoko(uint8_t annoksia) {
_annoskoko = annoksia;
}
uint8_t get_annoskoko(void) {
return _annoskoko;
}
Globaaleihin muuttujin otsikkotiedostoissa liittyen kannattaa mainita
extern
-avainsana. Tätä avainsanaa käytetään ilmoittamaan muuttuja tai funktio otsikkotiedostossa ilman, että sitä määritellään (varaamatta tilaa muistissa). extern
-avainsana kertoo kääntäjälle, että muuttuja tai funktio on määritelty jossain muualla, tyypillisesti .c-tiedostossa, ja sallii muiden tiedostojen käyttää sitä ilman uudelleenmäärittelyä.Externin käyttäminen auttaa jakamaan globaaleja muuttujia useiden lähdetiedostojen kesken ja varmistaa, että muuttuja määritellään vain kerran, mikä estää linkittäjän virheet, jotka johtuvat useista määritelmistä.
// pasta.h
#ifndef __PASTA_H_
#define __PASTA_H_
#include <stdio.h>
// Ilmoita globaali muuttuja käyttäen extern-avainsanaa
extern uint8_t _annoskoko;
// Funktioiden prototyypit
void set_annoskoko(uint8_t annoksia);
uint8_t get_annoskoko(void);
#endif
// pasta.c
#include "pasta.h"
// Määrittele globaali muuttuja (varaa muistia ja alusta se)
// Muut lähdetiedostot voivat nyt käyttää tätä muuttujaa extern-ilmoituksen kautta otsikkotiedostossa.
uint8_t _annoskoko = 0;
// Funktioiden toteutukset
void set_annoskoko(uint8_t annoksia) {
_annoskoko = annoksia;
}
uint8_t get_annoskoko(void) {
return _annoskoko;
}
{{{hightlight=c
// main.c
// main.c
- include "pasta.h"
int main(void) {
// Aseta ja hae globaali muuttuja
set_annoskoko(5);
uint8_t annoskoko = get_annoskoko();
printf("Annoskoko on: %d\n", annoskoko);
// Aseta ja hae globaali muuttuja
set_annoskoko(5);
uint8_t annoskoko = get_annoskoko();
printf("Annoskoko on: %d\n", annoskoko);
return 0;
}
}
Jos määrittelen globaalin muuttujan otsikkotiedostossa näin:
{{{ highlight c
// pasta.h
{{{ highlight c
// pasta.h
- ifndef __PASTA_H_
- define __PASTA_H_
- include <stdio.h>
// Määrittele globaali muuttuja suoraan (ilman externia)
uint8_t _annoskoko = 0;
uint8_t _annoskoko = 0;
// Funktioiden prototyypit
void set_annoskoko(uint8_t annoksia);
uint8_t get_annoskoko(void);
void set_annoskoko(uint8_t annoksia);
uint8_t get_annoskoko(void);
- endif
ja sitten sisällytän kirjaston molempiin tiedostoihin:
// pasta.c
#include "pasta.h"
}}}
{{{highlight=c
// main.c
#include "pasta.h"
jokainen tiedosto määrittelee oman versionsa _annoskoko-muuttujasta. Linkittäjä heittää sitten virheen
multiple definition of
.Jos vakio on esitelty otsikkotiedostossa, se voidaan ottaa vapaasti käyttöön useissa eri koodimoduleissa. Esimerkiksi,
math.h
-otsikkotiedostossa on piin arvolle määritelty vakio, jonka pitäisi tietysti olla koko ohjelmassa.// math.h:n sisällä..
#define M_PI 3.14159265358979323846
Tämän vakion käyttö omassa ohjelmassa ei tosin toimi, ellemme ole ensin määritelleet vakiota
#define _USE_MATH_DEFINES
, jos työskentelemme Windows-ympäristössä.Kirjaston toteutus¶
Kirjaston lähdekoodi tehdään omaan koodimoduliinsa, ts. tiedostoon (tässä
pasta.c
), joka se sisältää sitten kirjastojen jaettujen ja sisäisten funktioiden toteutukset. #include "pasta.h" // Pakottaa kirjaston funktiot noudattamaan prototyyppejä
// Kirjaston sisäiset muuttujat
uint8_t _vesimaara = 0;
uint8_t _keittoaika = 0;
uint8_t _annoskoko = 0; // Siirsimme muuttujan tänne otsikkotiedostosta!!
// Funktiot
void set_annoskoko(uint8_t annoksia) {
_annoskoko = annoksia;
}
uint8_t get_annoskoko(void) {
return _annoskoko;
}
uint8_t lisaa_vesi(void) {
_vesimaara = _annoskoko * 0.2;
}
void aseta_keittoaika(void) {
switch(_valittu_pasta) {
case SPAGETTI:
_keittoaika = 10;
break;
case MAKARONI:
_keittoaika = 8;
break;
...
}
}
C-kielessä ei kirjastofunktiota käytettäessä tarvitse määritellä, minkä kirjaston funktio on kyseessä. Tietenkään eri kirjastoissa ei saisi olla samannimisiä funktioita, jotta kääntäjä ei mene sekaisin että mitäs funktiota tässä ollaankaan kutsumassa..
C:n stardardikirjasto¶
C:n standardikirjasto sisältää ensinnäkin iso joukon määrittelyjä, jotka tukevat (ANSI-)standardin mukaisen C-kielen toteutusta eri laitealustoilla ja käyttöjärjestelmissä.
Standardikirjaston osien toteutus tulee (varsinkin kaupallisten) kääntäjäympäristöjen mukana valmiiksi binääriksi käännettynä ja tällöin toteutus on usein valmistajan optimoima. Näin ollen, valmiita funktioita kannattaa siis käyttää aina kuin mahdollista! Pyörän uudelleenkeksimisestä, varsinkaan siellä työelämässä sitten, ei saa pomolta bonuspisteitä.
C-kielen standardikirjastoista löytyy yleiskuvaus mm. Wikipediasta C:n Standardikirjasto tai oppikirjoista.
Muutamia hyödyllisiä kirjastoja:
- inttypes.h: Hyödyllisiä johdettuja muuttujatyyppejä
- stdio.h: Luku- ja kirjoitusfunktioita, tiedostonkäsittely
- stdlib.h: Tyyppimuunnokset, muistinkäsittely, järjestelmäkomentoja
- string.h: Merkkijonojen käsittely
- ctype.h: Yksittäisen merkin käsittely
- time.h: Kello ja kalenteri (yleensä ei toteutusta sulautetuissa järjestelmissä)
- math.h: Matemaattisia funktioita
Sulautetuissa järjestelmissä standardikirjastosta joudutaan laitteen suorituskyky- ja muistirajoitusten vuoksi jättämään joskus paljonkin osia pois. Tyypillisesti esimerkiksi
stdio.h
-kirjastoa on optimoitu siten, että rajoitetaan liukulukujen tarkkuutta ja printf
-funktiossa ole toteutettu liukulujen tulostamista.. Yleistä kirjastoista¶
Ohjelmakokonaisuuden kasvaessa funktiot kannattaa jaotella kirjastoiksi, joka selkeyttää pääohjelmaa huomattavasti, tekee siitä helpommin hallittavan ja ylläpidettävän.
Kirjastoihin jako myös helpottaa testausta ja virheenmetsästystä (ts. debuggausta) laajemmissa ohjelmissa. Jokainen kirjasto voidaan moduulina testata erikseen ennen sen käyttöönottoa ohjelmassa. Ajonaikaisia virheitä metsästäessä kirjastoja voidaan debuggerissa tarkastella erillisenä kokonaisuutena.
Ohjelmia voi jakaa kirjastoihin usealla eri tapaa, ja se paras tapa riippuu paljolti siitä keneltä asiasta kysyy. Tässä on esitelty yksi näkemys:
- Alustakohtaiset funktiot ja määrittelyt: sulautettujen ohjelmoinnissa laitealustalle täytyy yleensä kirjoittaa hyvinkin spesifejä funktioita, joille ei - ainakaan ilman suurempaa muokkausta - ole muille laitealustoille ohjelmoitaessa mitään hyötyä. Tällaiset laitekohtaiset funktiot ja määrittelyt kannattaa sijoittaa omaan kirjastoonsa, jolloin samaa ohjelmaa toiselle alustalle kääntäessä lähdekoodissa ei ole ns. ylimääräistä tavaraa.
- Sovelluskohtaiset funktiot ja määrittelyt: Ohjelmoidessa on aina hyvä pyrkiä modulaarisuuteen, mutta kaikista funktioista on hyvin vaikeaa tehdä mahdollisimman yleiskäyttöisiä eikä niiden sellaiseksi pakottaminenkaan ole järkevää. Tällaiset sovelluskohtaiset funktiot ja määrittelyt on hyvä sijoittaa omaan kirjastoonsa.
- Yleiskäyttöiset funktiot ja määrittelyt: Funktiot ja määrittelyt joita uskot voivasi käyttää jossakin toisessa ohjelmistoprojektissa jollekin toiselle laitteelle kannattaa sijoittaa omaan kirjastoonsa.
Lopuksi¶
Kurssilla tulemme käyttämään paljon standardikirjastoa sekä laitteemme ja RTOS SDK:n
tarjoamia valmiita kirjastoja laitteen ohjelmointiin
tarjoamia valmiita kirjastoja laitteen ohjelmointiin
Myös harrastelijat toteuttavat kirjastoja eri toiminnalisuuksille ja sulautettujen oheiskomponenteille, joten Google-haku on tässä kaveri. Tosin, usein on syytä pieneen varovaisuuteen, kun ei tiedä mitä oletuksia tai muita ehtoja kirjaston koodiin on asetettu. Eli ulkopuolinen koodi on hyvä käydä läpi ensin itse, ennekuin sen ottaa käyttöön omassa ohjelmassa. Lisäksi, koodin kommenteissa voi esimerkiksi olla arvokasta tietoa kirjaston käytöstä.
Anna palautetta
Kommentteja materiaalista?