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. 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ä. 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. Näin muuttuja itse olisi kapseloituna kirjaston sisäiseksi muuttujaksi, eikä sitä tarvitse mainostaa maailmalle otsikkotiedostossa.
void set_annoskoko(uint8_t annoksia) {
_annoskoko = annoksia;
}
uint8_t get_annoskoko(void) {
return _annoskoko;
}
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än vakion käyttö ei tosin toimi, ellemme ole ensin määritelleet vakiota
#define _USE_MATH_DEFINES
.)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" // 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, jotta kääntäjä ei mene sekaisin että mitäs funktiota tässä ollaankaan kutsumassa..
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 valitulle laitteelle käännettynä ja tällöin toteutus on valmistajan optimoima. Joten, näitä valmiita funktioita kannattaa siis käyttää aina kuin mahdollista! Pyörän uudelleenkeksimisestä, varsinkaan siellä työelämässä sitten, ei saa pisteitä.
C-kielen standardikirjastoista löytyy kuvaus mm. Wikipediasta C:n Standardikirjasto tai oppikirjoista.
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 muistirajoitusten 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 helposti 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 erillisenä kokonaisuutena, vrt. koko ohjelman läpi kahlaaminen.
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 SensorTag:n reaaliaikakäyttöjärjestelmän RTOS:n tarjoamia valmiita kirjastoja laitteen ohjelmoinnissa. SensorTagin oheislaitteille pitää yleensä kirjoittaa ja lukea niin paljon erilaista dataa, esimerkiksi laitteen konfiguroimiseksi , ettei näiden komentojen käsin kirjoittaminen itse ole mielekästä.
Myös harrastelijat toteuttavat kirjastoja eri toiminnalisuuksille ja sulautettujen oheiskomponenteille, joten Google-haku on tässä kaveri. Tosin, näiden käytössä on usein syytä pieneen varovaisuuteen, kun ei tiedä mitä oletuksia yms ehtoja kirjaston koodiin on asetettu. Eli ulkopuolinen koodi on hyvä käydä läpi ensin itse, ennekuin sen ottaa käyttöön omassa ohjelmassa. Koodin kommenteissa voi esimerkiksi olla arvokasta tietoa kirjaston käytöstä.
Anna palautetta
Kommentteja materiaalista?