Syöte- ja tulostus C-kielessä¶
Osaamistavoitteet: Tämän materiaalin luettuasi osaat tuottaa muotoiltua tulostusta ja tiedät mihin tarkoitukseen se on hyödyllistä sulautetuissa järjestelmissä.
Aiemmassa materiaalissa olemme jo onnistuneet tulostamaan tekstiä tietokoneen ruudulle C-kielellä
printf
-funktion avulla. Seuraavaksi paneudutaan asiaan hieman syvemmälti ja katsotaan miten tehdään muotoiltua tulostusta. Tässä yhteydessä esitellään C:n standardikirjaston stdio muotoiltuun tulostukseen ja syötteen lukemiseen tehdyt valmiit funktiot. Koska funktiot ovat osa standardia, lähtökohtaisesti ne toimivat kaikissa järjestelmissä samalla tavoin. Sulautetuissa järjestelmissä esiintyy yleisesti erilaisia standardikirjastojen toteutuksia, joissa kaikkea toiminnallisuutta ei ole mukana. Resurssirajoitusten vuoksi esimerkiksi liukulukujen käsittely, tulostus ruudulle, syötteiden kysely näppäimistöltä, jne, on jätetty pois. Standardikirjaston sijaan usein tarjotaan oheislaitteille omia kirjastoja, jotka toteuttavat vastaavan toiminnallisuuden, esimerkiksi funktioita merkkijonon tulostukseen LCD-näytölle.
Emme vielä tässä vaiheessa tiedä kaikkea C-kielen erikoisuuksista, mutta pärjäämme silti tämän materiaalin funktioiden kanssa aivan mainiosti.
Muotoiltu tulostus¶
C-kielessä on useita funktioita, joilla voidaan tuottaa muotoiltua tulostusta hieman eri tarkoituksiin. Käsittelemme tässä kaksi yleistä tapausta, joista jälkimmäinen on tärkeä tietää tehokkaassa sulautettujen ohjelmoinnissa.
Funktio printf¶
Funktio
printf
tulostaa työasemissa merkkijonon valitulle oheislaitteelle (näytölle) sen tulostusvuohon, josta käyttöjärjestelmä sitten poimii tuotoksemme ja toimittaa sen oheislaitteelle. Katsotaanpa esimerkkiä printf:n käytössä..
uint8_t a=97;
printf("Luku on %d ja se plus yksi on %d\n", a, a+1);
// Tämä tulostaa näytölle
Luku on 97 ja se plus yksi on 98
Tarkemmin funktion
printf
syntaksi on:printf("muotoilumerkkijono", argumentti1, argumentti2, ..., argumenttiN);
Muotoilumerkkijono kertoo, millaisen merkkijonon haluamme tulostaa. Merkkijono voi sisältää valmista tekstiä, ohjausmerkkejä (rivinvaihto jne) tekstin tulostamiseen ja sekä muuttujien arvoja (ja vakioiden arvoja). Muuttujien osalta muotoilumerkkijono kertoo miten muuttujien arvot muokataan tulostukseen osana merkkijonoa.
Yllä esimerkissä muotoilumerkkijono on
"Luku on %d ja plus yksi on %d\n"
. Nyt on siis sekaisin tekstiä ja muuttujan (tässä a
ja a+1
) arvoja. Paikkamerkki %
kertoo, että tähän kohti tulostusta tulee muuttujan arvo ja prosenttimerkin perässä olevat lisätiedot sen miten arvo tulostetaan. Esimerkissä paikkamerkillä %d
kerromme että halutaan tulostaa muuttujan arvo kokonaislukuna. Näitä paikkamerkkejä (muotoilumääreitä) C-kielessä koko onkin joukko, joista tässä alla yleisimmät.
Paikkamerkki | Muutujatyyppi |
d,i | kokonaisluku (int) |
u | kokonaisluku (uint) |
f | liukuluku (float, double) |
x | heksadesimaaliluku |
c | merkki (ASCII-koodin mukaan) |
s | merkkijono(char-taulukko) |
p | osoitin, muistiosoite (palataan tähän) |
% | prosenttimerkki |
Hox! Binääriluvun tulostamiseen ei siis ole standardissa suoraa muotoilua. Tässä onkin oiva harjoitustehtävä itseopiskeluun..
Esimerkki.
uint8_t x=97;
printf("x on kokonaislukuna %d\n",x);
printf("x on heksalukuna %x\n",x);
printf("x on liukulukuna %f\n",(float)x);
// Näytölle tulostuu..
x on kokonaislukuna 97
x on heksalukuna 61
x on liukulukuna 97.000000
Tässä tulee huomata, että
printf
itseasiassa tekee tarvittaessa tyyppimuunnoksen muuttujatyypistä muotoilun muuttujatyyppiin! Noh, jotta asiaan saadaan mutkia matkaan niin printf
-funktio tekee muunnoksen omapäisesti omia sääntöjään noudattaen ja siksi tulos ei ole aina toivottu. Näin ollen parempi itse pakottaa tyyppimuunnos. Yllä esimerkissä siis liukulukutulostus muuntamalla x
haluttuun muuttujatyyppiin operaattorilla (float)
.Ja kun käytämme johdettuja muuttujatyyppejä, saattaa kääntäjä joskus edellyttää tyyppimmuunnosta tai käännösvaiheessa tulee varoitus.
Muotoilun tarkentaminen voidaan tehdä lisätiedoilla:
Määre | Lisämuotoilu |
n | kokonaislukukentän vähimmäispituus (desimaaliosa ja -piste mukaanlukien), oikealle tasattu |
-n | kokonaislukukentän vähimmäispituus (desimaaliosa ja -piste mukaanlukien), vasemmalle tasattu |
0n | kokonaislukukentän vähimmäispituus (desimaaliosa ja -piste mukaanlukien), täytetään etunollilla |
.n | desimaaliosan vähimmäispituus |
l | kokonaisluvun pituus (long) |
h | kokonaisluvun pituus (short) |
+ | näytetään etumerkillä (+ tai -) |
Esimerkki.
uint8_t x=97;
printf("x on kokonaislukuna %05d\n",x);
printf("x on liukulukuna %.2f\n",x);
// Näytölle tulostuu..
x on kokonaislukuna 00097
x on liukulukuna 97.00
Merkkivakioilla voidaan ohjata tulostusta. Vakiot voivat paikkamerkin tapaan olla missä kohti tulostusta hyvänsä. Alla yleisiä merkkivakioita, näistäkään emme nyt tarjoa täydellistä esitystä.
Merkkivakio | Tarkoitus |
\n | rivinvahto |
\t | sarkain (tabulaattori) |
\\ | takakeno |
\? | kysymysmerkki |
\" | lainausmerkki |
Funktio sprintf¶
Alussa lupasimme käsitellä kaksi eri standardikirjaston tulostusfunktion versiota, joten esittelemme niistä nyt toisen eli
sprintf
-funktion. Tälle funktioille on erittäin hyödyllisiä käyttötarkoituksia sulautettuja järjestelmiä ohjelmoidessa. Nyt, tämä funktio toimii muuten samoin kuin
printf
, mutta siinä argumentiksi annetaan lisäksi merkkijono, johon tulostus menee talteen. Funktiolla siis muotoillaan tekstiä toiseen merkkijonoon.. ok, mutta miksi? Sulautetuissa järjestelmissä tulostukset ohjataan jollekin oheislaitteelle sen oman (ajuri)kirjaston kautta. Usein näihin ajurikirjastoihin ei ole toteutettu standardikirjaston läheskään kaikkia tulostusominaisuuksia, vaikka siellä ehkä onkin printf-niminen funktio. Kuten yllä todettiin, esimerkiksi liukulukujen tulostustoiminnallisuus saattaa puuttua kokonaan. Näin ollen
sprintf
on erittäin kätevä siksi, että voimme sen avulla muotoilla valmiiksi merkkijonon oheislaitteelle tulostusta varten. Oheislaitteen tulostusfunktio saa sitten valmiin merkkijonon argumentiksi, eikä sen tarvitse muokata tulostusta. Helppoa ja kätevää!Nyt syntaksissa
sprintf
eroaa siltäosin, että muotoilumerkkijonon eteen annetaan argumentiksi merkkijono, johon tulostus tallentuu. Esimerkki. Tulostetaan pi:n likiarvo LCD-näytölle. Oletetaan LCD:n kirjastossa olevan
lcd_printf
-funktio merkkijonon tulostamista varten. Määritellään myös LCD-näytön leveys merkkeinä vakioon LCD_MAX_WIDTH
joten nyt muuttujaan lcd_str
mahtuu juuri näytön rivin verran merkkejä.#define LCD_MAX_WIDTH 16
float pi = 3.14159;
char lcd_str[LCD_MAX_WIDTH];
sprintf(lcd_str,"%f\n",pi);
printf(lcd_str);
Tämä esimerkki kannattaa sisäistää, koska juuri näin tulemme toimimaan rakkaan SensorTagin kanssa myöhemmin kurssilla. Tästä lisää myöhemmin..
Hox! Tässä täytyy olla erityisen tarkkana, ettei tulostusmerkkijonon pituus ylity kun paikkamerkkejä korvataan argumenttien arvoilla. Esimerkiksi paikkamerkki
%08d
tulostaa kahdeksan merkkiä, kun paikkamerkin pituus itsessään on neljä merkkiä. Tällainen ylivuoto (tässä kirjoittaminen merkkijonon ohi) voi olla vaikeasti jäljitettävä bugi, koska funktio saattoi ylikirjoittaa muistissa jonkun toisen muuttujan arvon, joka sitten sekoittaa ohjelman toiminnan ja parhaassa tapauksessa laitteenkin. Sulautettua asiaa¶
Katsotaanpas seuraavaksi mitä muuta, ja varsin oleellista, hyötyä muotoillusta tulostuksesta on sulautettuja järjestelmiä ohjelmoidessa.
Ohjelman debuggaus¶
Funktio
sprintf
on yllämainitun käyttötarkoituksen lisäksi korvaamaton apu sulautetun järjestelmän toiminnan debuggaamisessa, eli toiminnan ajonaikaisessa seuraamisessa ja virhetilanteiden selvittelyissä. Muistetaan ensimmäiseltä luennolta, että sulautetun ohjelman suorittamista laitteessa voitiin kontrolloida debuggeri-laitteen avulla. Verrattuna työasemaohjelmointiin, on sulautetun ohjelman suoritusta ja fyysisen laitteen sielunelämää vaikea seurata "etänä" työasemalta. Näinollen debuggerit yleensä tarjoaa (erilaisia) keinoja lähettää viestejä laitteelta debuggerin kautta ohjelmointiympäristölle. Esimerkiksi merkkijonoja voidaan laitteessa pyörivästä ohjelmasta tulostaa ohjelmointiympäristön konsoli-ikkunaan. Näin meidän rakas SensorTagimmekin toimii.
Esimerkki. Nyt vaikkapa
for
-silmukassa voidaan tarkastella lähes reaaliajassa keräämiämme anturidatan arvoja. Oletetaan, että kiihtyvyysanturin eri akselien arvot on jo haettu muuttujiin acc_x
, acc_y
ja acc_z
, joista ne tulostetaan merkkijonoon debug_str
. Funktio System_printf
tulostaa SensorTagilta CCS-kehitysympäristön konsoli-ikkunaan, kuten tulemme myöhemmässä materiaalissa näkemään..char debug_str[80];
...
sprintf(debug_str,"%.2f %.2f %.2f",acc_x,acc_y,acc_z);
System_printf(debug_str);
...
Hox! Ette arvaakaan kuinka vuosien aikana moni harjoitustehtävissä ja -työssä ollut bugi on selvinnyt debug-välitulostusten avulla. Tässä monesti aloitteleva ohjelmoija voi ajatella, että eipäs kun minä selvitän tämän nyt päässäni, johon menee sitten aikaa. Näin voi kurssilla toki tehdä, mutta sitten siellä työelämässä ammattilaisetkin mieluummin turvautuvat ohjelman toiminnan lokitukseen, josta ongelma voi olla huomattavasti helpompi bongata. Isommissa ohjelmistoissa siellä työelämässä lokitus usein on ainoa vaihtoehto selvittää bugeja, varsinkin jos ohjelma koostuu useasta yhtaikaa ajettavasta modulista.
Pilkku on tärkeä¶
Sulautetussa maailmassa (ja muuallakin) toisiinsa liittyvää informaatiota, kuten vaikkapa kiihtyvyysanturin eri akselien arvot samalla ajan hetkellä, on hyödyllistä käsitellä yhtenä pakettina (tietorakenteena) ohjelmissa. Tietorakenteihin mennään tarkemmin myöhemmässä materiaalissa, mutta katsotaanpa tässä miten
sprintf
-funktio liittyy asiaan. Yleisesti toisiinsa liittyvän tiedon välittämiseen on tietotekniikassa esitelty merkkijonopohjainen Comma Separated Value (CSV)-formaatti, jossa tietorakenteen eri kenttien erottimena toimii pilkku. CSV:stä on esitetty eeri käyttöön soveltuvia standardeja, mutta niitä ei tarvitse kurssilla tietää. Selkeitä käyttötarkoituksia CSV-formaatille sulautetuissa järjestelmissä on merkkijonopohjainen kommunikointi oheislaitteiden kanssa sekä langaton viestintä.
Esimerkki. Yllämainitun kiihtyvyysanturin eri akselien arvot voitaisiin esittää CSV-muodossa seuraavasti, missä pilkulla erotetut numeroarvot vastaavat akselien arvoja.
0.342,0.665,0.734
Huomataan, että tällaista CSV-muotoisia tiedonvälitystä on C-kielessä helppo toteuttaa
sprintf
-funktion avulla. Esimerkki. Jälleen kiihtyvyysanturin 3:n akselin arvot on kerätty talteen muuttujiin
acc_x
, acc_y
ja acc_z
ja tehdäänpäs niistä CSV-muotoinen merkkijono ylläolevan esimerkin mukaan. char message[80];
sprintf(message,"%.3f,%.3f,%.3f\n",acc_x,acc_y,acc_z);
Hox! Kurssin harjoitustyössä tullaan tarvitsemaan
sprintf
-funktiota tässä käyttötarkoituksessa ihan varmasti.. Syötteen käsittely¶
No, vielä asiasta toiseen, eli
stdio
-kirjaston funktioilla voimme myös kysyä näppäimistösyötettä käyttäjältä (tai itseasiassa muultakin oheislaitteelta). Laiteläheisessä ohjelmoinnissa hyvin harvoin, jos koskaan, käytetään näitä funktiota joten tämä materiaali vain tiedoksi..Tässä funktio
getchar
tarjoaa keinon kysyä käyttäjältä syötettä merkki kerrallaan. Esimerkki. Seuraavalla
do-while
-rakenteella käyttäjältä kysytyt merkit tallentuvat muuttujaan c
yksi kerrallaan, kunnes käyttäjä painaa Enter-näppäintä (suom. palautusnäppäin..).uint8_t c = 0;
do {
c = getchar();
} while (c != '\n');
..eli on
do-while
-rakenteella ne kätevät puolensakin. Jos tässä haluaisimme talteen koko syötteen, pitäisi se tallentaa merkkijonoon yksittäisen merkkimuuttujan sijasta. Huomataan, että kun työasemalla testaamme ohjelmaa, näppäimistö kaiuttaa näppäinpainallukset ruudulle näkyviin. Eri käyttöjärjestelmissä voimme toki ohjata miten konsoli-ikkunan syöte näkyy ruudulla ja kaiutus saadaan tarvittaessa pois päältäkin, esimerkiksi kun kysymme käyttäjältä salasanaa.
Muotoiltu syöte¶
Funktio
scanf
on standardikirjaston funktio, jolla voidaan kysyä käyttäjältä muotoiltua syötettä ja tallentaa se muuttujiin halutussa muodossa. Asia vain tiedoksi, kurssilla emme käytä tätä funktiota.Funktion esittely on samankaltainen kuin
printf
:n, pienillä, mutta oleellisilla eroilla, kun käytössä on samat muotoilut. Lisäksi muuttujien eteen funktiokutsussa tulee &
-operaattori, mutta palataan siihen myöhemmin. scanf(muotoilumerkkijono, &muuttuja1, &muuttuja2, ...);
Esimerkki. Kysytään käyttäjältä työasemassa kokonaisluku ja tulostetaan se näytölle.
uint32_t luku=0;
scanf("%ld",&luku);
printf("%ld\n",luku);
Funktiolla
scanf
voidaan kysyä muotoiltuja syötteitäkin, mutta muotoilu on helppo rikkoa, koska funktio itsessään ei voi kontrolloida mitä käyttäjä oikeasti näppäilee. Alla, jos käyttäjä ei syötteessä noudattaisi funktiolle annettuja lisämääreitä, esim. numeron pituus suurempin kuin kaksi merkkiä, menisi funktio sekaisin.uint8_t paiva=0;
char kuukausi[12];
uint16_t vuosi=0;
printf("Anna paivamaara muodossa: 24.Joulukuuta 2017? ");
scanf("%2d.%s %4d", &paiva, kuukausi, &vuosi);
Vähemmän yllättäen, vastaavasti syötteen merkkijonoon tallennukseen löytyy funktio
sscanf
. Lopuksi¶
Enemmissä määrin
printf
:n muotoilun salaisuuksista kiinnostuneille lisätietoa löytyy mm. dokumentista Secrets of printf.Anna palautetta
Kommentteja materiaalista?