3.2. OLIOT 31 Myös tästä menetelmästä on olemassa muunnelmia, jotka pyrkivät vähentämään yksittäisen pysähdyksen pituutta. Nämä ovat niinsanottuja ikäperustaisia (generational) menetelmiä, joissa muisti jaetaan useampaan puoliavaruuspariin. Yksi niistä on lastentarha, johon uudet oliot synnytetään. Se siivotaan usein, koska useimmat oliot kuolevat nuorina. Pitkäikäisimmät lapset ylennetään samalla seuraavaan puoliavaruuteen (sukupolveen), jota siivotaan harvemmin. Samaan tapaan ylennetään siitäkin pitkäikäisimmät seuraavaan sukupolveen, kunnes kaikki puoliavaruusparit on käyty läpi. Sukupolvien määrä riippuu toteutuksesta. Merkille pantavaa näissä menetelmissä on se, että olion osoite voi muuttua sen elinaikana. Tosin algoritmit pitävät kyllä huolen siitä, että kaikki viitteet säilyvät ehjinä siirrosoperaation yli, eli kaikki osoitteet päivitetään osoittamaan olion uutta paikkaa. Ajonaikaiset tietorakenteet Toimiakseen siivousalgoritmit tarvitsevat ajonaikaista tukea. Ensinnäkin kaikki osoitteet on kyettävä erottamaan luotettavasti kokonaisluvuista. Dynaamisesti tyypitettyjen ja tyypittömien kielten toteutuksissa tämä on tapana hoitaa laputuksella: varataan merkityksettömin bitti (bitti 0) lapuksi, joka on nolla luvuilla ja yksi osoittimilla. Useissa järjestelmissä osoitteet ovat aina vähintään kahdella jaollisia (ja joissakin järjestelmissä parittomat pyöristetään alaspäin parillisiksi), jolloin tuo pieni virhe osoitteessa ei haittaa yhtään mitään. Aritmetiikka luvuilla puolestaan onnistuu lähes ilman muutoksia, kun alin bitti on nolla (lukuarvo saadaan siirtämällä bittejä yksi oikealle). Staattisesti tyypitettyjen kielten toteutukset jättävät monesti siivoimen käyttöön staattisia muuttujia, jotka kuvailevat kaikkien tyyppien rakenteen, erityisesti sen, missä kohtaa oliota osoittimet sijaitsevat. Jotkin toteutukset jopa räätälöivät siivoimen osia (esimerkiksi merkkaa ja lakaise -siivoimen merkkausosan) kullekin tyypille erikseen, jolloin mitään ajonaikaista tietoa ei tarvita. On myös mahdollista kirjoittaa siivoin, joka toimii ns. vihamielisessä ympäristössä. Boehmin, Demersin ja Weiserin siivoin 4 on tästä hyvä esimerkki: se toimii C- ja C++-kielten siivoimena ilman mitään tukea kääntäjältä. Erityisesti se ei tiedä, mitkä osoittimelta näyttävät otukset ovat osoittimia ja mitkä eivät. Se tekee ns. konservatiivisuusoletuksen: kaikki osoittimelta näyttävät ovat osoittimia. Tämä toimii, koska siivoin ei siirtele olioita ympäriinsä. Yleensä oletuksesta seuraava muistivuotokin on hyvin vähäistä. 4. http://www.hpl.hp.com/personal/hans_boehm/gc/
32 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT Säilyvät oliot Jotkin oliot ovat säilyviä (persistent), eli ne ovat syntyneet ennen ohjelman suorituksen alkamista tai kuolevat joskus ohjelman suorituksen päättymisen jälkeen. Tällaiset oliot elävät massamuistissa (esimerkiksi kovalevyllä) ja käyvät työmuistissa lähinnä toimiakseen operaatioiden kohteina. Eräässä mielessä ohjelman konekielinen koodi muodostaa säilyvän olion. Joissakin kielissä (esimerkiksi Smalltalk) kaikki oliot ovat säilyviä. Tällöin tyypillisesti koko käytössä oleva muisti on säilyvää, ja ohjelman suorituksen päättyminen vastaa lähinnä ohjelman suorituksen keskeyttämistä. Useat kielet (esimerkiksi Java) tarjoavat mahdollisuuden olion serialisointiin, jolla jokin ei-säilyvä olio voidaan tallentaa levylle erityisellä operaatiolla niin, että se voidaan taas uudestaan sieltä ladata, taas erityisellä operaatiolla. 3.2.3 Formaali käsittely Formaalisti muisti esitetään funktiona olion identiteetistä arvoon: ρ: Idty Value. Kukin olio esitetään siis yhtenä parina kuvauksessa ρ. Tässä abstrahoidaan pois koko tavujonokysymys ja olion tyypin käsittäminen tyyppinä. Tyypin ainoa jäljelle jäävä tarkoitus on kertoa, mitä arvoja olio voi pitää sisällään ja mitä operaatioita siihen voidaan kohdistaa. Olion elinaika muotoillaan siten, että ennen olion luontia ja sen kuoleman jälkeen sen arvona on : olio o on syntymätön tai kuollut, mikäli ρo =. 3.2.4 Olio-ohjelmoinnin oliokäsite Olio-ohjelmoinnin olioihin (viittaan näihin alempana lyhyemmällä termillä OO-olio) pätee kaikki edellä sanottu, mutta niihin liittyy muutakin. OO-olioita voidaan kuvata kahdella sanalla: tila (state) ja käyttäytyminen (behaviour). Tilalla tarkoitetaan sitä, mitä edellä kutsuttiin olion arvoksi. OO-olion käyttäytyminen puolestaan viittaa tiettyihin etuoikeutettuihin operaatioihin, metodeihin (methods): metodit kulkevat käsi kädessä olioiden kanssa, ne (tai oikeastaan osoite niiden ohjelmakoodiin) tallennetaan fyysisestikin joko itse olioon tai sitten erilliseen luokkaolioon, jonka osoite on puolestaan tallennettu olioon. Tämä luokkaolio (joka voi olla yleinen olio tai itsekin OO-olio) voi tallentaa muutakin kuin pelkän tiedon metodeista se, mitä kaikkea siellä tallennetaan, riippuu kielestä ja sen toteutuksesta. 3.2.5 Funktio-ohjelmoija ei näe olioita Funktio-ohjelmointikielissä oliot ovat pellin alla piilossa: funktio-ohjelmat operoivat pelkästään arvoilla, ja kielen kääntäjä muuntaa tämän toiminnan (ylei-
3.3. ESITTELYT 33 siä) olioita käyttäväksi. 3.3 Esittelyt Jokainen nimi, joka ohjelmassa esiintyy, on esiteltävä. Joskus esittely on implisiittinen. Lähes kaikissa kielissä on nimiä, jotka on määritelty kielen määrittelydokumentissa ja joilla on kielen suunnittelijan määräämä merkitys, vaikka kyse ei olisikaan varatusta sanasta. Joissakin kielissä, esimerkiksi Basicissä ja Perlissä (ilman use strict -määrittelyä), nimen ensimmäinen esiintymä on sen esittely. 3.3.1 Näkyvyysalue Jokaiseen nimen esittelyyn liittyy sen vaikutusalue (scope), jolla tarkoitetaan niitä osia ohjelmatekstistä, joissa kyseisellä nimellä on se merkitys, joka esittelyllä sille annettiin. Vaikutusalueen ulkopuolella tuo nimi voi tarkoittaa aivan jotain muuta. Joissakin tilanteessa vaikutusalueenkin sisällä tuo merkitys voi olla jonkin toisen esittelyn peittämä (shadowed), jolloin kyseisellä nimellä on tuon toisen esittelyn antama merkitys. Jokaiseen ohjelmatekstin kohtaan liittyy sen viiteympäristö (reference environment), joka luetteloi kaikki ne nimet ja niiden merkitykset, joiden esittelyiden vaikutusalueeseen tuo kohta kuuluu ja jotka eivät ole peitettyjä. Viiteympäristö siis kertoo, mikä merkitys milläkin nimellä on tietyssä kohtaa ohjelman suoritusta. Toteutusteknisesti viiteympäristöä vastaa toteutuksen ylläpitämä symbolitaulu (symbol table). Useimmat nykykielet ovat lohkorakenteisia. Tällaisten kielten ohjelmateksti on hahmotettavissa hierarkkisena kokonaisuutena: koko teksti jakautuu eri moduleihin (tai paketteihin), jotka puolestaan saattavat jakautua luokkiin, sitten tulevat aliohjelmat, aliohjelmat puolestaan saattavat sisältää uusia aliohjelmia tai luokkia sekä sisäkkäisiä lohkoja. Lohkorakenteiset kielet voidaan jakaa kahteen luokkaan vaikutusalueiden käyttäytymisen perusteella: kielet, joissa on käytössä staattiset vaikutusalueet (static scoping, lexical scoping), sekä kielet, joissa on käytössä dynaamiset vaikutusalueet (dynamic scoping). Joissakin harvoissa kielissä (lähinnä Common Lisp) ohjelmoija voi valita nimen esittelyn yhteydessä, käytetäänkö tuon esittelyn tapauksessa staattista vai dynaamista vaikutusaluetta. Staattinen vaikutusalue alkaa esittelystä ja jatkuu tekstuaalisesti sen lohkon loppuun, joka oli sisin esittelyn kohdalla. Mikään muu osa ohjelmatekstistä ei
34 LUKU 3. MUUTTUJAT, ARVOT, OLIOT JA TYYPIT kuulu vaikutusalueeseen. Mikäli saman nimen jonkin toisen esittelyn vaikutusalue on voimassa esittelyn kohdalla, peittää tämä esittely koko vaikutusalueensa ajan tuon toisen esittelyn. Staattinen vaikutusalue on täysin määrätty jo ennen ohjelman suorituksen alkamista. Dynaaminen vaikutusalue eroaa staattisesta siten, että esittelyn vaikutusalue ulottuu niihin aliohjelmiin, joita kutsutaan vaikutusalueen ollessa voimassa ja peittämätön. Vaikutusalue riippuu siis siitä, mitä suoritusaikana tapahtuu. Dynaaminen vaikutusalue on tapa antaa aliohjelmille näkymättömiä parametreja: dynaamisen vaikutusalueen ollessa käytäntönä voidaan esimerkisi kirjoittaa seuraavasti (kuvitteellisella, demoissa käsitellyn kielelen muunnelmalla): begin var print_base; print_base := 16; call print 42; end Tämä ohjelma siis tulostaisi 2A; aliohjelma print toimii eri tavoin sen mukaan, mikä on muuttujan print_base arvo. Tämä voi näyttää hyödylliseltä, mutta käytännössä dynaamisen vaikutusalueen periaate aiheuttaa ongelmia, kun kutsujan muuttujamäärittelyt yllättäen vaikuttavatkin kutsutun aliohjelman toimintaan. Vaara on jopa suurempi kuin globaalien muuttujien tapauksessa. Dynaaminen vaikutusalue tuli käyttöön Lispin mukana. Kielen kehittäjä John McCarthy piti sitä bugina, mutta silti tuo ominaisuus jäi eloon. Vasta tuoreet Lisp-murteet, kuten Scheme ja Common Lisp, käyttävät oletuksena staattisen vaikutusalueen periaatetta. Juuri muut kielet kuin Lisp ja sen murteet eivät käytä dynaamisen vaikutusalueen periaatetta. Modulit ovat poikkeus tiukkaan lohkorakenteisuuteen. Tyypillisesti modulit asettavat saataville (export) osan esittelyistään, ja toiset modulit ottavat niitä käyttöön (import). Tällöin näiden esittelyjen vaikutusalueeseen kuuluvat paitsi oma moduli myös käyttöönottavat modulit. Joissakin kielissä (kuten Java) kaikkien modulien kaikki saataville asettamat esittelyt ovat käytettävissä suoraan kaikissa moduleissa, jolloin ei tarvita erillistä käyttöönottoa. Tällöin yleensä kunkin nimen eteen tarvitaan muissa moduleissa sen modulin nimi, josta tuo nimi on peräisin. Vaikutusalueeseen liittyvät käytännöt riippuvat varsin paljon kielestä. Edellä esitettiin lähinnä yleisiä suuntaviivoja.
3.4. MUUTTUJAT 35 3.3.2 Sidonta Esittely sitoo (binds) nimeen joitakin ominaisuuksia (attributes). Jotkin ominaisuudet ovat staattisia: nämä ominaisuudet ovat täysin määrättyjä jo ennen suoritusaikaa. Loput ominaisuudet ovat dynaamisia ja ovat osittain tai kokonaan määrättävissä vain suoritusaikana. Esimerkiksi symboliset vakiot ovat nimiä, joihin liittyy yksi oleellinen staattinen ominaisuus: arvo. 3.4 Muuttujat Muuttujat ovat nimiä. Jokaiseen muuttujaan on sidottu olio. Muuttujan arvolla tarkoitetaan muuttujaan sidotun olion arvoa. Käytännössä muuttujan sidonta olioon toteutetaan siten, että muuttujaan liittyy staattisena ominaisuutena olion osoite. Joissakin tilanteissa se on kuitenkin toteutettu laatikoinnilla (boxing): muuttujaan liittyy apuolio, joka sisältää pelkästään tuon varsinaisen olion osoitteen. Tällöin kaikki muuttujan kautta tapahtuva olion käsittely tapahtuu apuolion kautta (niin, että tämä välikäden kautta kulkeminen ei näy muuttujan käyttäjälle). Jokaiseen muuttujaan liittyy ominaisuutena myös tyyppi. Mikäli tämä ominaisuus on dynaaminen, puhutaan dynaamisesti tyypitetystä kielestä. Tällöin muuttuja voi periaatteessa olla sidottu mihin tahansa olioon sen tyypistä riippumatta, ja muuttujan tyyppi on sama asia kuin muuttujan olion tyyppi. Mikäli muuttujan tyyppi on aina staattinen ominaisuus, puhutaan staattisesti tyypitetystä kielestä. Tällöin muuttujan olion tyypin tulee olla aina sama kuin muuttujan tyyppi (joissakin kielissä sallitaan joissakin tai kaikissa tilanteissa, että olion tyyppi on muuttujan tyypin alityyppi). Muuttujan käsitteeseen liittyy keskeisenä sijoitusoperaation (assignment) käsite. Sijoituksen perusideana on muuttaa muuttujan arvoa. Tähän on kaksi tapaa: joko sijoitus käy muuttamassa olion arvoa tai sitten se sitoo muuttujan uuteen olioon. Edellisessä tapauksessa kielessä sanotaan olevan käytössä arvosemantiikka (value semantics), jälkimmäisessä tapauksessa viitesemantiikka (reference semantics). Käytännössä viitesemantiikan toteuttaminen vaatii, että kaikki muuttujat ovat laatikoituja. Viitesemantiikkakielissä voi olla muita operaatioita, jotka muuttavat olion arvoa. Muuttuja voi olla sidottu olioon, jonka ainoana sisältönä on jonkin toisen olion osoite. Tällaista muuttujaa sanotaan osoittimeksi. Osoittimen arvoksi sanotaan sitä osoitetta, joka on osoittimeen sidotun olion sisältönä. Osoitin voi olla myös nollaosoitin (null pointer), josta tiedetään, ettei se osoita mihinkään olioon.