Osoittimet

Osoitintietotyypit ovat tietotyyppejä, joiden arvojoukko koostuu tietyntyyppisten tietoalkioiden muistiosoitteista. Toisin sanoen osoitintyyppiseen muuttujaan (osoittimeen, pointteriin) voidaan tallettaa muistiosoite. Osoittimeen voidaan siis tallettaa jonkin tarpeellisen tiedon sijainti muistissa.

Kuten jo aiemmin opimme, C++:ssa osoittimia määritellään:

kohdetyyppi* muuttujan_nimi;

eli aivan kuin oltaisiin määrittelemässä normaalisti kohdetyyppi-tyyppistä muuttujaa, mutta muuttujan nimen ja tyypin nimen väliin lisätään *-merkki.

Tuloksena saadaan muuttuja, johon voidaan tallettaa kohdetyyppi-tyyppisen muuttujan muistiosoite (sen sijainti muistissa). Kohdetyyppi on välttämätön, sillä sen perusteella osoittimeen talletetusta muistiosoitteesta löytyvää dataa (bittejä) osataan käsitellä oikein.

Kaikista ohjelmassa käytetyistä muuttujista saadaan tarvittaessa selville niiden muistiosoite unaarisella &-operaattorilla. Osoittimeen talletetussa muistiosoitteessa sijaitsevan muistipaikan sisältöön päästään käsiksi unaarisella *-operaattorilla. Unaarisen *-operaattorin toimintaidea on analoginen sen kanssa, kuinka sitä käytettiin iteraattoreiden kanssa.

Tutkitaan yksinkertainen esimerkki:

#include <iostream>

int main() {
    int i;
    int* ip = nullptr;  // Palataan nullptr:iin tämän materiaaliosion lopussa
    i = 5;
    ip = &i;
    *ip = 42;
    std::cout << i << "," << *ip << "," << ip << "," << &ip << std::endl;
}
Luodaan kokonaislukumuuttuja i, siis varataan sille tilaa muistista jostain vapaasta kohdasta. Kuvitellaan nyt, että muisti varataan vaikkapa osoitteesta 0x000002.
Luodaan osoitin kokonaislukuun ip, siis varataan sille tilaa muistista jostain vapaasta kohdasta. Kuvitellaan, että tämä muisti varataan kohdasta 0x000008.
Sijoitetaan muuttujalle i arvo 5, siis talletetaan luvun 5 binääriesitys osoitteesta 0x000002 alkaviin muistipaikkoihin.
Selvitetään muuttujan i osoite (0x000002) &-operaattorilla ja talletetaan se muuttujaan ip eli osoitteesta 0x000008 alkaviin muistipaikkoihin.
Sijoitetaan ip:hen talletetusta osoitteesta (0x000002) alkaviin muistipaikkoihin luvun 42 binääriesitys (samat muistipaikat, joissa muuttuja i on tallessa, siis muuttujan i arvo muuttuu).
Koska osoitin ip on muuttuja, senkin sijainti muistissa saadaan &-operaattorilla. Kuvitteellisessa esimerkissämme näytölle tulostuu 42,42,0x000002,0x000008. Todellinen tuloste tietenkin vaihtelee tietokoneesta, käyttöjärjestelmästä ja suorituskerrasta riippuen.

Kuvallisesti esimerkin muistinkäyttö näyttäisi main-funktion lopussa seuraavalta:

../../_images/muisti_05.png

Esimerkissä on oletettu osoittimen olevan kooltaan 4 tavua eli 32-bittiä. Todellisuudessa osoittimet ovet useimmissa nykyisissä tietokonelaitteissa 64-bittisiä.

Esimerkistä havaitaan myös, että lausekkeessa käytettynä  *ip käyttäytyy kuin int-tyyppinen muuttuja: Sille evaluoituu arvo muistipaikasta, johon ip osoittaa, ja siihen voidaan suorittaa sijoitus operaattorilla =, jolloin arvo tallettuu kyseiseen muistipaikkaan. Sama pätee muunkin tyyppisiin osoittimiin.

Osoittimen arvo kertoo, missä jokin tieto sijaitsee. Koska itse muistipaikan numero ei ole olennainen, esitetään edellisen kuvan sisältö usein yksinkertaistettuna tähän tapaan:

../../_images/osoitinkuva1.png

Kuvaamalla osoitinta nuolella saadaan esitettyä, minne se osoittaa ilman epäolennaisen muistipaikan numeron esittämistä. Tämä kuva esittää siis täsmälleen saman asian kuin edellinen kuva, mutta tästä on abstrahoitu muistipaikan numero pois kuvaamalla osoittamista nuolella.

Operaattorilla * osoitimesta saadaan selville varsinainen osoitettu tieto. Sanomattakin(?) on selvää, että osoittimeen voi tallentaa vain sellaisen muuttujan osoitteen, jonka tyyppi on sama kuin osoittimen kohdetyyppi.

Esimerkissä ip-osoittimeen alustetaan arvo nullptr. Tämä on tärkeä pieni yksityiskohta. Tällaista osoitinta kutsutaan null- tai nil-pointteriksi. Kaikkiin osoitinmuuttujiin voidaan sijoittaa nullptr. Osoittimelle saadaan näin asetettua arvo, joka ei osoita minnekään. Osoittimen yhtä- ja erisuuruutta voi vertailla nullptr-arvon kanssa ja nullptr:ää voi käyttää osoitintyyppisenä parametrina. Usein myös funktiot, joiden paluuarvo on tyypiltään osoitin, palauttavat virhetilanteessa nullptr:n.

Jos ohjelman suorituksen aikana yritetään seurata [*] null-osoitinta, on tuloksena aina ajonaikainen virhe ja ohjelman suorituksen päättyminen. Null-osoitinta kannattaa aina käyttää osoitinmuuttujien alustusarvona ja virhepaluuarvona funktioilta, joiden paluuarvon tyyppi on osoitin.

Jos osoittimeen on talletettu class- tai struct-tyyppisen arvon muistiosoite, osoitetun tiedon metodeja ja kenttiä käsitellään -> operaattorilla.

[*]Eli tutkia tietoa, johon osoitin osoittaa. Eli kohdistaa osoittimeen unäärinen * -operaattori tai -> -operaattori.