Osoittimet¶
Osaamistavoitteet: Tämän materiaalin läpikäytyäsi tiedät miten osoittimia käytetään C-kielessä ja mitä merkittävää hyötyä niiden käytöstä on muistinhallinnassa laiteläheisessä ohjelmoinnissa.
Otetaanpa alkuun vähän kertausta. Kun esittelemme ohjelmassamme muuttujan, vaikkapa
int8_t x = 42;
tapahtuu seuraavaa:- Kääntäjä varaa muistista muistipaikan, jonka koko riippuu muuttujatyypistä. Tässä yksi tavu.
- Kääntäjä tallettaa varattuun muistipaikkaan muuttujan alustusarvon, eli tässä luvun 42.
- (Kääntäjä tekee sisäisen taulukon, missä muuttujan nimi kuvataan sen muistiosoitteena, niin että käännetyssä konekielisessä ohjelmassa muuttujanimen tilalta on sen muistiosoite. Noh, ei mennä tähän tarkemmin kuin vasta kurssin TKJ-osuudessa.)
Osoitinmuuttuja¶
Osoittimet / osoitinmuuttujat (myös pointteri, engl. pointer) ovat tapa osoittaa / viitata suoraan ohjelmasta itsestään sen käytössä olevaan muistiin. Aikaisemmin osoitimme muistiin esittelemällä ohjelmassa muuttujia, joiden arvoja käsittelimme koodissa ikäänkuin "suoraan". Sen sijaan osoitinmuuttujalle voimme ohjelmassa asettaa muistipaikan, minne se osoittaa ja käsitellä siellä olevaa arvoa. Tällä tavoin pääsemme käsiksi mihin tahansa muistipaikkaan ohjelmalle varatussa muistissa.
Selvennetään asiaa kuvitteellisen esimerkin kautta, jossa olemme ohjelmassa esitelleet ja alustaneet kaksi muuttujaa
a
ja osoitin_a
.uint8_t a = 0x42;
uint8_t *osoitin_a = &a; // &-operaattori
Okei tässä nyt ei ole (vielä) mitään meille outoa, kaksi muuttujaa
a
ja osoitin_a
, joilla on omat muistipaikat muistissa (kuvitteelliset muistiosoitteet 0x8E
ja 0x8F
):Muuttujatyyppi | Muuttujan nimi | Muistipaikan osoite | Arvo |
uint8_t | a | 0x8E | 0x42 |
uint8_t* | osoitin_a | 0x8F | 0x8E |
Nyt eroa syntyykin siitä, että miten näiden muuttujien arvot tulkitaan:
- Muuttujan
a
arvo0x42
ymmärretään ohjelmassa sellaisenaan. - Osoitinmuuttujan
osoitin_a
arvo0x8E
tulkitaan tähtioperaattorin*
avulla muistiosoitteeksi.
Esimerkkiä tarkastellessa huomaamme, että osoitinmuuttujan
osoitin_a
arvo onkin itseasiassa muuttujan a
muistipaikan osoite! Tällöin sanotaan että se osoittaa / viittaa muuttujaan a
. Osoittaminen tarkoittaa, sitä että osoitinmuuttujaa käytettäessä ikäänkuin hypätään sen arvon kertomaan muistipaikkaan. Itse muuttujaa a
voimme edelleen käsitellä "tavallisen" muuttujan tavoin, mikään ei sen suhteen muutu.Esimerkki. Katsotaas miten käy seuraavassa:
#include <stdio.h>
#include <inttypes.h> // Johdetut muuttujatyypit täältä
int main() {
uint8_t a = 0x42;
uint8_t *osoitin_a = &a;
printf("Muuttujan a arvo %x\n",a);
printf("Muuttuja osoitin_a osoittaa arvoon %x\n",*osoitin_a);
// Muutetaan a:n arvoa sijoituksella
a = 0x56;
printf("Muuttuja osoitin_a osoittaa arvoon %x\n",*osoitin_a);
// Muutetaan a:n arvoa osoittimen kautta sijoituksella
*osoitin_a = 0x78;
printf("Muuttujan a arvo %x\n",a);
return 0;
}
Ja tämähän tulostaa:
Muuttujan a arvo 42 Muuttuja osoitin_a osoittaa arvoon 42 Muuttuja osoitin_a osoittaa arvoon 56 Muuttujan a arvo 78
Nyt osoitinmuuttujat tarjoavat epäsuoran tavan osoittaa jollekin muulle muuttujalle varattua muistia. Kuten tulemme näkemään, tästä on valtavasti hyötyä tehokkaamman ja tiiviimmän koodiin kirjoittamisessa, joka on erityisen tärkeää sulautettujen ohjelmoinnissa resurssirajoitusten takia. (Lisäksi, koska osoitinmuuttujan arvo voi olla mikä tahansa luku, niin voimme osoittaa minne tahansa ohjelman käytössä olevaan muistin.)
Käytämmekin tämän luentokappaleen loppuosan selvittämään mitä hyötyä tällaisesta kikkailusta on.
Osoitinoperaattorit¶
Ensin kuitenkin hieman C-kielen syntaksia. Kieli tarjoaa kaksi operaattoria osoitinmuuttujien kanssa toimimiseen. Operaattoreilla voidaan selvittää minkä tahansa muuttujan osoite, alustaa osoitinmuuttujia haluttuun osoitteeseen sekä noutaa osoitettujen muistipaikkojen arvot.
Operaattori &¶
Operaattoria
&
käytetään kysymään miltä tahansa muuttujalta sen osoite muistissa. Operaattorin syntaksi on &muuttujan_nimi
. Katsotaanpa koodiesimerkki luennoitsijan verovähennyskelpoisessa koti-PC:ssä:int8_t a = 12;
printf("a:n arvo on %d ja muistiosoite %p",a,&a);
..jonka tulostus kertoo, että
a:n arvo on 12 ja muistiosoite 000000000023FE47
(suorittimessa 64-bittinen arkkitehtuuri).Operaattori *¶
Operaattorilla
*
on kolme käyttötarkoitusta. 1. Osoitinmuuttuja esitellään operaattorilla
*
, eli syntaksi *muuttujan_nimi
. int8_t *osoitin_a = &a;
HOX: Jos ilmoitat osoittimen mutta et alusta sitä, osoittimen arvo on määrittelemätön, eli se sisältää roskadataa. Yritys dereferoida (päästä käsiksi arvoon, johon se osoittaa) johtaisi määrittelemättömään toimintaan.
int main() {
int8_t *pointer_a; // Ilmoitettu mutta ei alustettu
printf("Alustamattoman osoittimen arvo: %p\n", pointer_a); // Tämä voi tulostaa minkä tahansa satunnaisen osoitteen
return 0;
}
Sen sijaan, jos emme halua alustaa osoitinta sen määrittelyn yhteydessä, meidän pitäisi alustaa se arvolla
NULL
.int8_t *pointer_a = NULL; // Nyt pointer_a on eksplisiittisesti alustettu arvolla NULL
2. Sijoitusoperaattori
*
jolla voidaan sijoittaa uusi arvo osoitettuun (toisen) muuttujan muistipaikkaan. int8_t a = 47;
int8_t *osoitin_a = &a;
*osoitin_a = 23; // muutetaan osoittimen kautta a:n arvoa
Nyt siis alustetaan
a
arvoon 47 ja sitten osoittimella osoitin_a
sijoitetaan siihen uusi arvo 23. Jännää..Vrt. Sijoitus ilman
*
-operaattoria osoitin_a = 23
muuttaisi osoitinmuuttujen itsensä arvoa, niin että osoittaisikin muistipaikkaan 23 eikä enää (välttämättä) muuttujan a muistipaikkaan. 3. Osoitinmuuttujalla ja
*
-operaattorilla noudetaan osoitinmuuttujan osoittaman muistipaikan arvon. uint16_t x = 0xBEEF;
uint16_t *osoitin_x = &x;
printf("x=%x\n",*osoitin_x);
Joka tulostaa noudetut osoitetut arvot.
x=beef
Osoitinmuuttujan tyyppi¶
Huomataan yltä, että aina osoitinmuuttujalle annetaan myös muuttujatyyppi kun se esitellään.. miksi? Eikös se ole muistiosoite?
Muuttujatyyppi tarvitaan siksi, että kääntäjä tietäisi minkä tyyppiseen arvoon osoitin viittaa siellä muistipaikassa. Katsotaanpa allaolevaa koodiesimerkkiä.
int main() {
uint32_t a = 0x12345678;
uint8_t *osoitin_byte = &a; // 8-bit osoitin
uint16_t *osoitin_word = &a; // 16-bit osoitin
uint32_t *osoitin_longword = &a; // 32-bit osoitin
printf("%x\n",*osoitin_byte);
printf("%lx\n",*osoitin_word);
printf("%lx\n",*osoitin_longword);
return 0;
}
Koodissa esitellään siis 32-bittinen kokonaislukumuuttuja ja sille 8-, 16- ja 32-bittiset osoittimet. Kun sitten tulostetaan (luennoitsijan pc:ssä) jokaisen osoittimen osoittama muuttujan arvo, niin saadaan:
78 // 8-bittinen osoitin palauttaa tavun 5678 // 16-bittinen osoitin palauttaa sanan 12345678 // 32-bittinen osoitin palauttaa pitkän sanan
Havaitaan että kääntäjä siis ymmärtää osoittimen sen muuttujatyypin mukaan ja noutaa sen mukaisen arvon muistista! Nyt siis osoitinmuuttuja on syytä esitellä samalla muuttujatyypillä kuin sen osoittama muuttuja.
Hox! Esimerkkikoodia kääntäessä eteen voi tulla varoitus vääräntyyppisen osoittimen esittelemisestä
warning: initialization from incompatible pointer type
. Hox! Miksi tulostus ei ole 12 tai 1234?? Tässähän vaikuttaa suoritinarkkitehtuurin tavujärjestys.
Osoitinmuuttuja muistissa¶
Tietysti osoitinmuuttuja, kuten kaikki muuttujat, tarvitsee oman muistipaikkansa. Palataan yo. esimerkkikoodiin hieman muokattuna. Huomatkaa paikkamerkki
%p
, jolla voidaan tulostaa muistiosoitteita. #include <stdio.h>
#include <inttypes.h>
int main() {
uint8_t a = 0x12;
uint8_t *osoitin_a = &a;
// uusi operaattori: sizeof
printf("muuttujan a muistiosoite: %p\n",&a);
printf("muuttujan koko tavuina: %d\n",sizeof(a));
printf("osoitinmuuttujan muistiosoite: %p\n",&osoitin_a);
printf("osoitinmuuttujan koko tavuina: %d\n",sizeof(osoitin_a));
return 0;
}
Joka tulostaa (muistiosoitteet nyt oikeita):
muuttujan a muistiosoite: 0x7ffd6232a93f muuttujan koko tavuina: 1 osoitinmuuttujan muistiosoite: 0x7ffd6232a940 osoitinmuuttujan koko tavuina: 8
Esimerkin tulostuksesta nähdään, että jokaisella osoitinmuuttujalla on oma osoitteensa muistissa ja meille uuden
sizeof
-operaattorin avulla saadaan osoitinmuuttujan muistipaikan koko (8 tavua -> 64-bittinen suoritinarkkitehtuuri). Oleellista tässä siis on hoksata että osoitinmuuttujan itsensä muistipaikan koko on tässä 4 tavua, riippumatta siitä että se osoittaa yhden tavun kokoista muuttujaa. Osoittimien käyttäminen¶
Okei. hieno homma. Mutta mihin osoitinmuuttujaa sitten tarvitaan?
Osoittimet ja taulukkomuuttujat¶
Tarkastellaanpa osoitinmuuttujien ja taulukkojen läheistä suhdetta.
Koska osoittimien kanssa on vapaat kädet rellestää ohjelmalle varatussa muistissa, voimme tietysti alustaa osoitinmuuttujia osoittamaan myös taulukon alkioihin.
char merkkijono[] = "XYZ";
char *ptr_1 = &merkkijono[0]; // nyt osoittaa merkkiin X
char *ptr_2 = &merkkijono[1]; // nyt osoittaa merkkiin Y
char *ptr_3 = &merkkijono[2]; // nyt osoittaa merkkiin Z
printf("%c%c%c\n", *ptr_1, *ptr_3, *ptr_2);
Tulostaa:
XZY
Osoittimien alustamisessa taulukon kanssa on muutakin jännää.
int main() {
char merkkijono[] = "XYZ";
char *tapa_1 = &merkkijono[0]; // alustustapa 1
char *tapa_2 = merkkijono; // alustustapa 2 -> tulkitaan: decays
// Tulostetaan merkkijono
printf("%s\n", merkkijono);
printf("%s\n", tapa_1);
printf("%s\n", tapa_2);
return 0;
}
Koska kaikki kaksi alustustapaa osoittavat samaan muistiosoitteeseen, merkkijonon tulostus on sama.
XYZ XYZ XYZ
Voimme havaita, että C-kielessä, kun taulukkoa käytetään lausekkeessa, taulukon nimi tulkitaan (eng. decays) osoittimeksi sen ensimmäiseen alkioon. Tämä havainto tulee olemaan meille hyödyllinen pian...
Taulukot ja osoittimet eivät kuitenkaan ole identtisiä; taulukot ovat yhtenäinen muistilohko, kun taas osoitin sisältää jonkin muistipaikan osoitteen. On tärkeää huomata, että taulukon nimeä ei voi muuttaa osoittamaan toiseen paikkaan, toisin kuin osoittimet, joiden arvoa voi muuttaa.
Nyt tarkastellaan, mitä tapahtuu, kun taulukko välitetään funktiolle. Kun taulukko välitetään funktiolle, vain osoitin taulukon ensimmäiseen alkioon välitetään (taulukon nimi "tulkitaan" osoittimeksi), joten funktiolla ei ole tietoa taulukon koosta, ellet kerro sitä erikseen.
Tässä on esimerkki funktiosta, joka tulostaa kokonaislukutaulukon, ja meidän on välitettävä sekä taulukko että sen koko:
#include <stdio.h>
// Funktion prototyyppi: Ottaa osoittimen taulukkoon ja sen koon
void print_array(int *array, int size);
int main() {
int numbers[] = {10, 20, 30, 40, 50}; // Määritellään kokonaislukutaulukko
int size = sizeof(numbers) / sizeof(numbers[0]); // Lasketaan taulukon koko
// Kutsutaan funktiota taulukolla ja sen koolla
print_array(numbers, size);
return 0;
}
// Funktio taulukon alkioiden tulostamiseksi
void print_array(int *array, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
}
Hox: sizeof ei palauta taulukon alkioiden määrää, vaan taulukon kokonaismuistin koon tavuina. Jotta saisit oikean määrän alkioita, sinun tulee jakaa taulukon koko yhden alkion koolla, näin:
int size = sizeof(numbers) / sizeof(numbers[0]); // Laskee taulukon koon oikein tässä
Voit aina määritellä taulukon (esimerkiksi puskuri) koon vakioksi ja käyttää sitä argumenttina koon määrittelyssä.
Osoitin-aritmetiikkaa¶
Koska osoitinmuuttujien arvot ovat lukuarvoja, niitä voidaan tietysti käsitellä kaikilla C-kielen aritmeettisillä operaatioilla.
Esimerkiksi.
int main() {
char merkkijono[] = "ABCD";
char *ptr = merkkijono;
// tulostus merkki kerrallaan silmukassa..
for (ptr = merkkijono; *ptr != 0; ptr++) {
printf("%c",*ptr);
}
return 0;
}
Esimerkkikoodi tulostaa ruudulle.
ABCD
Nyt lienee hyvä paikka avata tätä esimerkkiä hieman:
- Alustuslauseessa
ptr = merkkijono
asetamme osoitinmuuttujanptr
arvoksi merkkijonon alkuosoitteen (muista: taulukon tulkinta osoittimeksi). - Ehtolauseessa
*ptr != 0
tarkistamme, ettei osoitin osoita merkkijonon viimeisenä alkiona olevaan lopetusnollaan (\0) (kts aiempi materiaali C-kielen muuttujista). Silmukka pyörii siis niin kauan, että*ptr:n
osoittama arvo on nolla. - Sijoituslausekkeessa
ptr++
kasvatamme osoittimen muistipaikan arvoa yhdellä. Toisinsanoen osoitin vain siirtyy osoittamaan seuraavaan muistipaikkaan.
Mikä on erinomaista, aritmeettiset operaatiot osoittimilla eivät välitä muuttujatyypistä.
Esimerkki. Tässä
++
-operaattorilla päästään aina seuraavaan alkioon sijoituslauseessa ptr++
. #include <stdio.h>
#include <inttypes.h>
int main() {
uint16_t taulukko[] = { 0x1234, 0x5678, 0x9ABC };
uint16_t *ptr = taulukko;
for (ptr = taulukko; *ptr != 0; ptr++) {
printf("%lx ",*ptr);
}
return 0;
}
Joka tulostaa:
1234 5678 9abc
Kun kasvatat osoittimen arvoa, sen siirtymän määrä riippuu siitä, minkä kokoiseen tietotyyppiin osoitin viittaa. Esimerkiksi, kun kasvatat char * -osoitinta, se siirtyy 1 tavun verran (koska char on 1 tavu), kun taas uint16_t * -osoittimen kasvattaminen siirtää sitä 2 tavua (koska uint16_t on 2 tavua).
Tässä myös korostuu se, kuinka C todellakin on matalan tason laiteläheinen kieli. Pääsemme käsiksi ohjelmamme muistiin vapaasti ja voimme sotkea muuttujien arvoja ja niiden muistipaikkoja.
Seurauksena tietysti C-ohjelmoijan on oltava varsin tarkkana, ettei osoittimilla pelatessa ohjelmassa rikkoudu mitään. Esimerkiksi saatamme vahingossa osoittaa muuttujan muistialueen ulkopuolelle, eli vahingossa muokata jonkin toisen muuttujan arvoa (ylivuoto), tai jopa koko sulautetun ohjelman muistialueen ulkopuolelle, josta seuraa esimerkiksi ohjelman kaatuminen.
Funktion argumentteina¶
Kuten kaikkia muitakin muuttujatyyppejä, myös osoittimia voidaan käyttää funktion parametreinä. Oleellisesti tällöin argumenttina annetaan muistiosoite, joka tallentuu siihen funktion paikalliseen muuttujaan. Tästä on suuresti hyötyä kuten kohta nähdään..
Lähdetään liikkeelle esimerkistä.. allaoleva funktio
vaihda
ei toimi. Miksi? #include <stdio.h>
void vaihda(int8_t local_a, int8_t local_b); // prototyyppi
int main() {
int8_t a = 14;
int8_t b = 68;
printf("Ennen: a=%d ja b=%d\n", a, b);
vaihda(a, b);
printf("Jälkeen: a=%d ja b=%d\n", a, b);
}
void vaihda(int8_t local_a, int8_t local_b) {
int8_t temp = local_a;
local_a = local_b;
local_b = temp;
}
Joka tulostaa:
Ennen: a=14 ja b=68 Jälkeen: a=14 ja b=68
Selitys asiaan löytyy luentokappaleen Funktiot C-kielessä -materiaalista. Muistamme, että C-kielessä funktio loi argumenteista kopiot paikallisiin muuttujiin. Ylläolevassa koodissa siis vain vaihdetaankin vain kopioiden arvoja (muuttujat
local_a
) ja local_b
, eikä alkuperäisten muuttujien a
ja b
arvoja. No, ei hätää. Asia korjataan käyttämällä osoittimia funktion argumentteina. Nyt funktiossa osoitin
local_a
osoittaakin argumenttina annettuun a
:n muistipaikkaan.#include <stdio.h>
void vaihda(int8_t *a, int8_t *b); // prototyyppi osoittimilla
int main() {
int8_t a = 14;
int8_t b = 68;
printf("Ennen: a=%d ja b=%d\n", a, b);
vaihda(&a, &b); // Hox &-operaattorin käyttö
printf("Jälkeen: a=%d ja b=%d\n", a, b);
}
void vaihda(int8_t *local_a, int8_t *local_b) {
int8_t temp = *local_a; // a:n osoittama arvo talteen
*local_a = *local_b; // b:n osoittama arvo osoitin a:n arvoon
*local_b = temp; // a:n osoittama arvo b:n osoittamaan muistipaikkaan
}
Nyt ohjelma tulostaa:
Ennen: main_a=14 ja main_b=68 Jälkeen: main_a=68 ja main_b=14
Muistin säästöä¶
Samalla tavoin osoittimilla voi myös merkittävästi säästää muistia!
Muistetaan esimerkki aiemmasta materiaalista, jossa välitimme
struct
:n, joka sisälsi suuren taulukon, funktion argumenttina, ja funktio-parka yritti kuuliaisesti tehdä siitä kopion joka kerta.// Määritellään struct viestiä varten
typedef struct {
char destination_address[4]; // 4 tavun osoite
char message[2048]; // 2 kilotavun viesti
} Message;
...
// Määritellään viesti-struct ja täytetään se datalla
Message message_home = {"ABCD", "Tämä on viestin sisältö..."};
...
// Kutsutaan funktiota ja välitetään struct argumenttina
send_message(message_home);
...
Tämä ongelma voidaan kätevästi korjata antamalla funktiolle struct:in osoite argumenttina:
// Määritellään struct viestiä varten
typedef struct {
char destination_address[4]; // 4 tavun osoite
char message[2048]; // 2 kilotavun viesti
} Message;
// Funktion prototyyppi
void send_message(Message *message_home);
int main() {
// Määritellään viesti-struct ja täytetään se datalla
Message message_home = {"ABCD", "Tämä on viestin sisältö..."};
// Kutsutaan funktiota ja välitetään struct argumenttina (viittauksena)
send_message(&message_home);
return 0;
}
Hox! Toki funktio tekee kopion osoitinmuuttujan arvosta, mutta onhan se osoitinmuuttujatyyppi (kooltaan 4 tai 8 tavua) paljon pienempi kuin itse taulukko.
Funktiosta palauttaminen¶
Palataan hetkeksi aiemmin esitettyyn
vaihda
-funktioon, nimittäin siitä hoksataan toinenkin jännä asia.. void vaihda(int8_t *a, int8_t *b) {
int8_t temp = *a;
*a = *b;
*b = temp;
}
Nythän tässä funktiossa itseasiassa palautamme funktion suorituksen tuloksena kaksi arvoa, eli vaihtuneet muuttujien
a
ja b
arvot. Tällä tavoin siis kierrettiin C-kielen rajoituksia käsittelemällä suoraan muistia muuttujien itsensä sijaan. Superkätevää! Hox! Funktiosta voimme tietenkin myös palauttaa muistiosoitteen. Mutta varmista, että osoitin viittaa kelvolliseen muistiin (esimerkiksi dynaamisesti varattuun muistiin tai globaaliin muuttujaan). Osoittimen palauttaminen paikalliseen muuttujaan funktiossa on turvatonta, koska muisti muuttuu virheelliseksi, kun funktio päättyy.
Merkkijonojen käsittely¶
Yksi oleellinen osoittimien hyöty ilmenee taul.. merkkijonojen käsittelyssä.
Standardikirjaston osana kirjasto string.h määrittelee joukon hyödyllisiä funktioita merkkijonojen kanssa pelaamiseen:
- Merkkijonon pituuden selvittäminen
strlen
- Merkkijonojen (merkkien) vertailu
strncmp
- Merkin tai merkkijonon etsiminen toisesta merkkijonosta
strstr
- Merkkijonojen tai niiden osien kopiointi
strncpy
- Merkkijonojen liittäminen toisiinsa
strncat
- Jopa merkkijonon purkaminen osiin!
strtok
- .. ja paljon muuta
#include <stdio.h>
#include <string.h>
int main() {
char nimi[] = "Judge Dredd";
printf("Nimen %s pituus on %d merkkiä\n",nimi, strlen(nimi));
return 0;
}
Nyt kaikki nämä kirjaston funktiot ottavat merkkijonoja sisäänsä osoittimina niiden muuttujanimen kautta. Eli ne olettavat annetun taulukon päättyvän aina nollaan ja siksi eivät tarvitse taulukon kokoa parametrina. Tämähän on tietysti vaarallista jos käsitellään merkkijonoja, jotka eivät pääty nollaan. Joten tarkkana.
Kätevyyttä laiteläheiseen¶
Loppuun vielä esimerkki
strtok
-funktiosta, koska siitä on sulatetuissa järjestelmissä valtavasti hyötyä. Kuten aiemmin todettiin, niin tietorakenteet ja langattomat viestit kulkevat usein CSV-muodossa, jossa siis viestin eri kentät ovat erotettu toisistaan pilkulla.
Esimerkiksi
1234567890,temperature,27,C
, jossa ensimmäinen kenttä olisi aikaleima (ts. milloin anturin arvo on mitattu), sitten anturin tyyppi (temperature), mkittausarvo (27) ja mittayksikkö (Celsius).Nyt tällaisen CSV-muotoisen merkkijonon purkaminen osiin hoituu helposti
strtok
-funktiolla.#include <string.h>
#include <stdio.h>
int main () {
char str[] = "1234567890,temperature,27,C";
const char sep[] = ","; // Erotin pilkku
char *token; // paikkamerkki-osoitin
// Erota viestistä ensimmäinen osa
token = strtok(str, sep);
// Erota silmukassa loput osat
while( token != NULL ) {
printf("%s\n",token);
// Funktio uudelleen, jatketaan paikkamerkistä
token = strtok(NULL, sep);
}
return(0);
}
Katsotaas mitä esimerkki tulostaa!
1234567890 temperature 27 C
Huom:
strtok
ei toimi intuitiivisella tavalla. Voit löytää lisäselvitystä sen toiminnasta täältä.Esimerkissä täytyy huomata, että erotetut pätkät ovat vielä tyyppiä merkkijono. Jotta voimme käsitellä viestissä olevia numeerisia arvoja numeroina, täytyy meidän muuntaa merkkijonot kokonais- tai liukulukumuuttujatyyppeihin.
Tähän standardikirjasto stdlib.h tarjoaa mm. funktiot
atoi
kokonaisluvuille, atol
pitkille kokonaisluvuille, ja atof
liukuluvuille. Osan näistä funktioista toiminnallisuus on vanhempien C standardien mukaista, mutta voimme käyttää niitä silti vielä. Modernit järjestelmät käyttävät strtol
ja strtod
.#include <stdio.h>
#include <stdlib.h>
int main () {
char str[] = "1234567890";
long arvo = atol(str);
printf("%ld\n",arvo);
return(0);
}
Tosin, jälleen kerran sulautetuissa kehitysympäristössä nämä standardikirjaston funktiot on saatettu korvata jollain toisilla funktioilla tai niiden toteutuksessa saattaa olla rajoituksia.
Lopuksi¶

Tässä materiaalissa oli kurssille riittävä johdatus osoittimiin C-kielessä. Niillä on vielä muitakin salaisuuksia. kuten komentoriviargumentit, (moniulotteiset) osoitintaulukot ja funktio-osoittimet, joita emme käy kurssilla läpi. Oppikirjoista löytyy toki osoittimista syvemmin kiinnostuneille lisätietoa.
Anna palautetta
Kommentteja materiaalista?