Kirjastot¶
Kirjastona käsitetään mitä tahansa koodimodulin ulkopuolista C-koodia, jota haluamme uudelleenkäyttää ohjelmassamme. Tyypillisesti kirjastot sisältävät monikäyttöisiä funktioita, esim. matemaattisia funktioita, joista on hyötyä monissa eri ohjelmissa. 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 liitetään ohjelmaamme käännösprosessin aikana. Kirjastolla kuitenkin 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ä. Periaatteessa otsikkotiedoston käyttäminen on sama kuin ohjelmaan kopioisi suoraan #include -käskyn tilalle kyseisen tiedoston sisällön.
Otsikkotiedosto
pasta.h
voisi olla esimerkiksi seuraava:#ifndef __PASTA_H_
#define __PASTA_H_
#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;
#endif
Globaalien muuttujien käyttäminen otsikkotiedostojen kautta on hieman kyseenalaista, parempaa koodia olisi tehdä erilliset asetus- ja lukufunktiot tällaisille muuttujille:
void set_annoskoko(uint8_t annoksia) {
_annoskoko = annoksia;
}
uint8_t get_annoskoko(void) {
return _annoskoko;
}
Muuttuja itse voisi tällöin olla kapseloituna kirjaston sisäiseksi muuttujaksi lähdekoodissa, eikä sitä tarvitse mainostaa maailmalle otsikkotiedostossa.
Jos vakio on esitelty otsikkotiedostossa, se voidaan ottaa käyttöön osana kirjastoa useissa eri koodimoduleissa. Esimerkiksi, ympäristöstä riippuen
math.h
-otsikkotiedostossa voi olla pii:n arvolle määritelty vakio// math.h:n sisällä..
#define M_PI 3.14159265358979323846
(Tämä ei tosin toimi, ellemme ole ensin määritelleet vakiota
#define _USE_MATH_DEFINES
.)Kirjaston lähdekoodi
pasta.c
sisältää sitten funktioiden toteutukset yms.#include "pasta.h" // Tämä pitää aina olla, jotta funktiot noudattavat 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.
C:n stardardikirjasto¶
C:n standardikirjasto sisältää määrittelyjä jotka tukevat ANSI-standardin mukaisen C-kielen toteutusta. Standardikirjastojen toteutus tulee kääntäjäympäristön mukana valmiiksi käännettynä ja tällöin toteutus on äärimilleen optimoitua. Joten näitä valmiita funktioita kannattaa käyttää.
C-kielen standardikirjastoista löytyy tietoja mm. Wikipediasta C:n Standardikirjasto.
Muutamia hyödyllisiä kirjastoja:
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 |
inttypes.h | Hyödyllisiä muuttujatyyppejä |
Tosin, sulautetuissa järjestelmissä joskus standardikirjastosta joudutaan koodin koon ja muistitilan vuoksi jättämään 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, ylläpidettävän ja modulaarisemman eli sen eri osia voi mahdollisesti uudelleenkäyttää jossakin myöhemmässä ohjelmistoprojektissa.
Kirjastoihin jako myös helpottaa testausta ja virheenmetsästystä (ts. debuggausta) laajemmissa ohjelmissa. Kirjasto voidaan moduulina testata erikseen ennen sen käyttöönottoa ohjelmassa. Ajonaikaisia virheitä metsästäessä kirjastoja voidaan debuggerissa tarkastella erikseen, jonka jälkeen vikatilanteessa suurella todennäköisyydellä ongelma on jossain toisaalla ohjelmassa.
Ohjelmia voi jakaa kirjastoihin usealla eri tapaa, ja se paras tapa riippuu paljolti siitä keneltä asiasta kysyy. Tässä on esitelty yksi tapa:
- 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ä sen sellaiseksi pakottaminenkaan ole järkevää. Tällaiset tietyn sovelluksen käyttämät 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.
Nämä kolme perusjoukkoa jakaantuvat vielä pienempiin osiin, esimerkiksi sovelluskohtaisten ja yleiskäyttöisten funktioiden rajamaastoon sijoittuvat tietynlaisen sovellusryhmän käyttämiin funktioihin. Tällaisia voisivat olla vaikkapa korttipakan luominen, sekoittaminen ja jakaminen pelaajille, näitä funktioita tarvitaan todennäköisesti aika monessa korttipelissä.
Lopuksi¶
Kurssilla tulemme (paljonkin) käyttämään standardkikirjastoa sekä CCS:n ja TI:n reaaliaikakäyttöjärjestelmän tarjoamia valmiita kirjastoja sulautetun laitteen ohjelmoinnissa. SensorTagin oheislaitteille pitää yleensä kirjoittaa ja lukea niin paljon erilaista dataa, esimerkiksi sen alkuasetuksia varten, ettei näiden komentojen käsin kirjoittaminen itse ole mielekästä.
Myös harrastelijat toteuttavat kirjastoja eri komponenteille, joten Google-haku on tässä kaveri. Tosin, näissä on usein syytä pieneen varovaisuuteen, kun ei tiedä mitä oletuksia yms ehtoja kirjaston koodiin on asetettu. Eli koodi on hyvä käydä läpi ensin itse.
Anna palautetta
Kommentteja materiaalista?