TIES542 kevät 2009 Suoraviivaohjelmat

Samankaltaiset tiedostot
Luku 3. Muuttujat, arvot, oliot ja tyypit. 3.1 Arvot

samalla seuraavaan puoliavaruuteen (sukupolveen), jota siivotaan harvemmin.

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Muistinhallinta ohjelmointikielissä

3.5. TYYPIT 43. g(x) muuten. että tämä funktio todella kuuluu funktioalueeseen.

4.2. ALIOHJELMAT 71. Tulosvälitteisyys (call by result) Tulosvälitteinen parametri kopioidaan lopuksi

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

11/20: Konepelti auki

Tietorakenteet ja algoritmit

Muistinsiivous. TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 30. marraskuuta 2009 TIETOTEKNIIKAN LAITOS. Muistinsiivous.

Ensimmäinen ohjelmointikieli

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Harjoitustyö: virtuaalikone

Java-kielen perusteet

Osoitin ja viittaus C++:ssa

lausekkeiden tapauksessa. Jotkin ohjelmointikielet on määritelty sellaisiksi,

3. Muuttujat ja operaatiot 3.1

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 27. lokakuuta 2009

Automaattinen muistinhallinta

Algoritmit 1. Luento 3 Ti Timo Männikkö

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmoinnin perusteet Y Python

Sisältö. 22. Taulukot. Yleistä. Yleistä

TIES542 kevät 2009 Tyyppiteorian alkeet

Java-kielen perusteet

7. Oliot ja viitteet 7.1

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ohjelmointikielten periaatteet Syksy Antti-Juhani Kaijanaho

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

7/20: Paketti kasassa ensimmäistä kertaa

Tietorakenteet ja algoritmit

Tietotekniikan valintakoe

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

MUISTINHALLINTA OHJELMOINTIKIELISSÄ

Sisältö. 2. Taulukot. Yleistä. Yleistä

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Ohjelmoinnin peruskurssi Y1

ELM GROUP 04. Teemu Laakso Henrik Talarmo

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Lyhyt kertaus osoittimista

Tieto- ja tallennusrakenteet

Osoittimet ja taulukot

5.6. C-kielen perusteet, osa 6/8, Taulukko , pva, kuvat jma

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Algoritmit 2. Luento 3 Ti Timo Männikkö

Tietotyypit ja operaattorit

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

ITKP102 Ohjelmointi 1 (6 op)

.NET ajoympäristö. Juha Järvensivu 2007

LOAD R1, =2 Sijoitetaan rekisteriin R1 arvo 2. LOAD R1, 100

Ongelma(t): Miten mikro-ohjelmoitavaa tietokonetta voisi ohjelmoida kirjoittamatta binääristä (mikro)koodia? Voisiko samalla algoritmin esitystavalla

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

1. Esittelyt ja vakiot 1.1 Esittelyt (declarations) Ennen nimen, tunnuksen (identifier) käyttöä se on

Ohjelmoinnin peruskurssi Y1

Ohjelmointi 1 Taulukot ja merkkijonot

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIEA255 Tietotekniikan teemaseminaari ohjelmointikielet ja kehitysalustat. Antti-Juhani Kaijanaho. 16. helmikuuta 2011

16. Ohjelmoinnin tekniikkaa 16.1

Ohjelmoinnin perusteet Y Python

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

TIEP114 Tietokoneen rakenne ja arkkitehtuuri, 3 op. Assembly ja konekieli

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 5. marraskuuta 2015

Algoritmit 1. Luento 1 Ti Timo Männikkö

TIEP114 Tietokoneen rakenne ja arkkitehtuuri, 3 op. Assembly ja konekieli

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

ITKP102 Ohjelmointi 1 (6 op)

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 12. tammikuuta 2012

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

sama tyyppi (joka vastaa kaikkien mahdollisten arvojen summa-aluetta). Esimerkiksi

A TIETORAKENTEET JA ALGORITMIT

Matematiikan tukikurssi

Tietueet. Tietueiden määrittely

Java-kielen perusteet

8. Näppäimistöltä lukeminen 8.1

Algoritmit 2. Luento 3 Ti Timo Männikkö

811120P Diskreetit rakenteet

Algoritmit 1. Luento 10 Ke Timo Männikkö

Tietorakenteet ja algoritmit - syksy

Sisällys. 16. Ohjelmoinnin tekniikkaa. Aritmetiikkaa toisin merkiten. Aritmetiikkaa toisin merkiten

Luento 2: Tiedostot ja tiedon varastointi

Tieto ja sen osoite (3) Jakso 3 Konekielinen ohjelmointi (TTK-91, KOKSI) Osoitinmuuttujat. Tieto ja sen osoite (5)

2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 29. toukokuuta 2013

Clojure, funktionaalinen Lisp murre

16. Ohjelmoinnin tekniikkaa 16.1

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

Ohjelmoinnin peruskurssi Y1

Transkriptio:

TIES542 kevät 2009 Suoraviivaohjelmat Antti-Juhani Kaijanaho 19. tammikuuta 2009 Suoraviivaohjelmilla (engl. straight-line programs) tarkoitetaan ohjelmia, joissa ei ole lainkaan silmukoita tai muunlaisia hyppyoperaatioita. Suoraviivaohjelmien hyödyllisyys sinänsä on toki varsin vähäistä, mutta jokainen imperatiivinen ohjelma koostuu joukosta hyppyoperaatioin tai silmukoin yhdistetyistä pienistä suoraviivaohjelmista (kääntäjätekniikassa niitä kutsutaan usein nimellä peruslohko, basic block). Oppi alkaa juuresta, joten on hyödyllistä aloittaa kielten tutkiskelu suoraviivaisten ohjelmien kielestä. Samalla voimme tutustua yleisemminkin siihen, mitä ohjelmointikielet oikeastaan ovat. Suoraviivaohjelmoinnin perusperiaate on seuraavanlainen: ohjelma koostuu lauseista 1 (engl. statements), jotka yhdistetään peräkkäistyksellä (engl. sequencing). Tärkein lause on sijoituslause (engl. assignment statement), joka asettaa muuttujalle (engl. variable) jonkin uuden arvon (engl. value), jonka se saa laskemalla jonkin tietyn lausekkeen (engl. expression) arvon senhetkisessä ympäristössä (engl. environment). 1 Arvo Arvot ovat abstrakteja, matemaattisia käsitteitä. Arvoja ovat esimerkiksi eri kokonaisluvut, merkkijonot ja muut sellaiset. Ne ovat ajattomia ja paikattomia: ei ole mielekästä pohtia, milloin nolla syntyi tai milloin sana "kaamos" lakkaa olemasta. Arvon lukumäärääkään ei voida mielekkäästi laskea (kuinka monta ykköstä on?). Arvot eivät myöskään muutu (oho, ykkönen onkin nyt kakkonen vai onko?). 1. Huomaa: käskyt (engl. instructions) kuuluvat konekieliohjelmointiin. Korkean tason kielissä puhutaan lauseista. 1

On tärkeää käsittää, että arvot eivät näy missään. Tietokoneohjelman lähdekoodissa esiintyvä merkkijono 42 ei ole arvo 42; se edustaa tai merkitsee (engl. denote) sitä. Myöskään ohjelman ruudulle tulostama Hei, Antti-Juhani ei ole merkkijonoarvo vaan se edustaa sitä. Tästä huolimatta arvoilla on merkittävä rooli tietokoneohjelmien teoriassa ja ymmärtämisessä. 2 Oliot 2.1 Olio muistialueena Eri tietokoneet tallentavat tietoa eri tavoin. Yhteistä kaikille yleisesti käytetyille tietokoneille on se, että koneen muisti on jaettu muistipaikkoihin, joilla on osoite ja jotka sijaitsevat muistissa peräkkäin (tosin muistin ei tarvitse olla yhtenäinen). Osoite esitetään tavallisesti kokonaislukuna. Kukin muistipaikka tallentaa yhden tavun. Tavulla (byte) tarkoitetaan tietynmittaista bittijonoa, joka riittää yhden merkin esittämiseen. Nykyisin sillä tarkoitetaan myös tietokoneen pienintä osoitettavissa olevaa muistiyksikköä, joka on enintään konesanan (machine word) kokoinen. Aiemmin on käytössä ollut 6-, 7- ja 9-bittisiä tavuja. Nykyään 8-bittinen tavu on yleisin, mutta 64-bittiset tavut eivät ole täysin tavattomia. Mikäli tarkoitetaan nimenomaan 8-bittistä tavua, tulisi puhua oktetista (octet). Ohjelmointikielen näkökulmasta tietokoneen muisti jakautuu olioihin (objects) ja vapaaseen muistiin. Kuhunkin olioon liittyy kolme ominaisuutta: osoite, tyyppi ja arvo. Olion arvo on kahden ensimmäisen ominaisuuden sekä tietokoneen muistin tilan funktio: olion osoitteesta koneen muistissa alkava olion koon (joka selviää olion tyypistä) pituinen tavujono tulkitaan tyypin mukaisesti olion arvoksi. Olioilla on myös identiteetti. Se ei näy ohjelmointikielen tasolla, vaan se on ohjelmistoanalyysin ja -suunnittelun väline, ja samoin sitä käytetään ohjelmointikielen tutkiskelussa. Identiteetti on puhdas samuuden abstraktio: jotta jokin olion ominaisuus olisi olion identiteetti, pitäisi seuraavien kolmen väitteiden pitää sille paikkaansa: 1. Jokaisella oliolla on identiteetti. 2. Eri olioilla on eri identiteetti. 3. Oliolla on koko olemassaolonsa aikana sama identiteetti. Se, mikä olion ominaisuus identiteetti varsinaisesti on, ei ole tässä oleellista oikean, yleispätevän vastauksen keksiminen on hyvin vaikeaa, vaikkakin kussakin erityistapauksessa sen löytäminen on yleensä ongelmatonta. Esimerkiksi olion osoite tuntuu houkuttelevalta vaihtoehdolta ja näin tehdäänkin monissa kielissä 2

mutta se ei yleisesti ottaen käy, koska olion osoite voi aivan hyvin vaihtua ohjelman suorituksen aikana. 2.2 Elinikä Toisin kuin arvot, oliot sijaitsevat ajassa. Oliolla on tietty syntyhetki ja tietty kuolinhetki; puhutaan sen elinajasta (lifetime). Olio voi 1. syntyä ohjelman alkaessa ja kuolla sen loppuessa (staattinen olio, static object), 2. syntyä tiettyyn ohjelmalohkoon tultaessa ja kuolla sieltä poistuttaessa (pinodynaaminen olio, stack-dynamic object), 3. syntyä erityisen luontioperaation ja kuolla erityisen tuhoamisoperaation vaikutuksesta (manuaalisesti tuhottava kekodynaaminen olio, manually deallocated heap-dynamic object), 4. syntyä erityisen luontioperaation vaikutuksesta ja kuolla joskus sitten, kun sitä ei enää kaivata (automaattisesti kuoleva kekodynaaminen olio, automatically deallocated heap-dynamic object), ja 5. syntyä jo ennen ohjelman alkamista tai kuolla vasta joskus ohjelman päättymisen jälkeen (säilyvä olio, persistent object). Kaikki ohjelmointikielet eivät tue kaikkia edellä mainittuja elinikätyyppejä. Esimerkiksi alkuperäisessä Fortranissa käytettiin vain staattisia olioita. Useimmat kielet eivät tue säilyviä olioita. Monet laajassa käytössä olevat kielet eivät tue automaattisesti kuolevia kekodynaamisia olioita, mutta lähes kaikki muut kielet puolestaan eivät tue manuaalisesti tuhottavia kekodynaamisia olioita. 2.2.1 Staattiset oliot Staattiset oliot syntyvät ohjelman suorituksen alkaessa ja kuolevat sen päättyessä. Käännettyjen ohjelmien ohjelmatiedostoissa näillä olioilla on oma paikkansa ohjelmakoodin rinnalla, ja näiden olioitten alkuarvo on usein ilmaistu jo ohjelmatiedostossa. Nämä oliot syntyvät ohjelman alkaessa siten, että ohjelmakoodi näine olioineen ladataan muistiin. Ne kuolevat, kun ohjelman ohjelmakoodi näine olioineen poistetaan muistista ohjelman suorituksen päätyttyä. Staattisten olioiden käyttäminen ajon aikana on yleensä tehokasta: niiden muistiosoite voidaan laskea linkitysvaiheessa, jolloin olioon viittaaminen voi usein tapahtua suoralla osoituksella (direct addressing). 2.2.2 Pinodynaamiset oliot Lähes kaikki ohjelmointikielten toteutukset varaavat ajonaikaisesta muistista alueen käytettäväksi pinona. Lähes kaikki prosessorit tukevat tätä varaamalla eri- 3

tyisen rekisterin (esimerkiksi IA32:ssa SP) pino-osoitinkäyttöön (osoittamaan ensimmäistä varaamatonta muistipaikkaa pinossa). Pinodynaamiset oliot ovat sellaisia otuksia, jotka luodaan tarvittaessa (esimerkiksi tiettyyn lohkoon tultaessa) ja jotka tuhotaan käänteisessä luontijärjestyksessä (esimerkiksi kyseisestä lohkosta poistuttaessa). Kielissä, joissa voidaan käsitellä osoittimia olioihin vapaasti, pinodynaamisiin olioihin liittyy ongelma: ne saattavat kuollessaan jättää jälkeensä orpoja osoittimia (dangling pointers). Mikäli tällaisia osoittimia käytetään, astutaan hyvin määritellyn toiminnallisuuden ulkopuolelle. (Aiemmin käsiteltyjen Hoaren kriteereiden mukaan ohjelmointikielen pitäisi suojata tätä vastaan.) Yksi klassinen esimerkki orpojen osoittimien ongelmasta kärsivästä kielestä on C. Esimerkiksi seuraava aliohjelma palauttaa orvon osoittimen: char const * readline(void) { char line[512]; fgets(line, sizeof line / sizeof *line, stdin); return line; } 2.2.3 Kekodynaamiset oliot Kekomuisti (heap memory) on muistialue, johon voi synnyttää ja tappaa kaikenkokoisia olioita milloin vain. Kekomuistista varattavien olioiden elinaika on periaatteessa rajattu vain ohjelman suorituksen alulla ja lopulla. Tällaisten, kekodynaamisten olioiden merkitys ohjelmoinnissa on yhä suurempi: lähes kaikki vähänkään monimutkaisemmat tietorakenteet vaativat käytännössä kekodynaamisten olioiden käyttöä. Huomaa, ettei kekodynaamisten olioiden käyttämiseen välttämättä tarvita tukea eksplisiittisille osoittimille. Kekodynaamiset oliot jaetaan kahteen alaluokkaan sen perusteella, pitääkö ohjelmoijan huolehtia niiden tuhoamisesta itse vai hoitaako sen kielen toteutuksen ajonaikainen osa (engl. runtime environment). Edellisiä voitaneen sanoa manuaalisesti tuhottaviksi, jälkimmäisiä automaattisesti kuoleviksi. Manuaalisesti tuhottavissa kekodynaamisissa olioissa on sama ongelma kuin pinodynaamisissa olioissa, mikäli osoittimet sallitaan: orpoja osoittimia syntyy aivan liian helposti. Automaattisesti kuolevat oliot toteutetaan muistinsiivousmenetelmillä 2 (garbage 2. Richard Jones ja Rafael Lins: Garbage Collection: Algorithms for Automatic Dynamic Memory Management, Chichester, Wiley, 1996. 4

collection methods). Perusalgoritmeja on kolme: viitelaskuritekniikka, merkkaa ja lakaise -tekniikka sekä pysäytä ja kopioi -tekniikka. Seuraavassa esitellään nämä kolme perusalgoritmia sekä niiden vaatimat ajonaikaiset tietorakenteet. Viitelaskuri Viitelaskuritekniikan (reference counting) perusidea on ylläpitää kussakin oliossa tietoa sen osoitteen kopioiden (viitteiden) lukumäärästä (ns. viitelaskuri). Laskuri alustetaan nollaksi. Joka kerta, kun sen osoite kopioidaan, kasvatetaan viitelaskuria yhdellä. Joka kerta, kun yksi kopio osoitteesta hävitetään, viitelaskuria vähennetään yhdellä, ja jos se menee nollaksi, olion sisältämät osoittimet nollataan (päivittäen rekursiivisesti vittattujen olioiden viitelaskureita) ja olio tapetaan. Viitelaskuritekniikan etuna on se, että tuhoamiset tapahtuvat täysin synkronoidusti: heti, kun viimeinen viite katoaa, oliokin tapetaan. Tämä mahdollistaa tuhoamishetken koukutuksen: ohjelmoija voi kirjoittaa aliohjelman, joka ajetaan, kun olio kuolee. Toisaalta, jos kyseinen olio on esimerkiksi ison puurakenteen juuri, joudutaan koko puurakenne tuhoamaan samalla kertaa, ja ohjelma pysähtyy joksikin aikaa. Jos kaksi oliota viittaavat toisiinsa, on kummankin viitelaskuri aina positiivinen. Eli jos viimeinenkin ulkopuolinen viite näihin olioihin katoaa, oliot jäävät edelleen elämään. Näin viitelaskuritekniikassa on pieni riski hallitsemattomaan muistivuotoon. Merkkaa ja lakaise Merkkaa ja lakaise (mark and sweep) on vanhin siivousalgoritmi ja edelleen varsin käyttökelpoinen. Sen perusidea on varata kustakin oliosta yksi bitti muistinhallinnan käyttöön. Tämä bitti on kaikissa olioissa normaalisti samanarvoinen (joko kaikilla päällä tai kaikilla pois). Kun muisti loppuu tai siivous joudutaan jostain muusta syystä aloittamaan, siivoin (collector) merkkaa (vaihtaa tuon bitin arvon toiseksi) kaikki staattiset ja pinodynaamiset oliot niiden sanotaan muodostavan juurijoukon (root set). Sitten se tekee saman kaikille niille olioille, joiden osoite on tallennettu johonkin jo merkattuun olioon. Kun kaikki merkattavat oliot on merkattu, siivoin käy koko muistin läpi ja tappaa ne oliot, jotka eivät tulleet merkatuksi (toisin sanoen ne oliot, jotka eivät kuulu juurijoukon transitiiviseen sulkeumaan). Tämä menetelmä kykenee poistamaan kaikki tarpeettomaksi käyneet oliot kunhan osoitteet muistetaan nollata, kun niitä ei enää tarvita. Menetelmän haittapuolena on se, ettei edellä mainittujen, kuoleman yhteydessä ajettavien aliohjelmien kirjoittaminen ole erityisen mielekästä. 5

Tämä metodi on tavallisesti maailman pysäyttävää tyyppiä kun siivous on käynnissä, kaikki muu laskenta on pysähdyksissä. Tämä aiheuttaa ongelmia monilla sovellusalueilla, joten tästä teemasta on kehitetty erityisiä vähittäisiä (incremental) muunnelmia, joissa tyypillisesti tehdään aina hieman lisää siivousta, kun muistia varataan. Pysäytä ja kopioi Pysäytä ja kopioi (stop and copy) -menetelmän perusideana on jakaa muisti kahteen yhtäsuureen alueeseen, puoliavaruuteen (semispace). Jompi kumpi niistä on aina lähdeavaruus (fromspace), toista sanotaan vastaavasti kohdeavaruudeksi (tospace). Uusille olioille varataan tila aina kohdeavaruudesta (jossa kaikki elossa olevat oliot sijaitsevat peräkkäin) pinovarauksen tyyliin. Jos varaus epäonnistuu, aloitetaan siivous. Tällöin ensin vaihdetaan puoliavaruuksien merkitykset: lähdeavaruudesta tehdään kohdeavaruus ja kohdeavaruudesta lähdeavaruus. Kaikki juurijoukkoon kuuluvien (eli staattisten ja pinodynaamisten) olioiden sisältämien osoitteiden päässä olevat oliot kopioidaan lähdeavaruudesta kohdeavaruuteen (ja korjataan nuo osoitteet osoittamaan kohdeavaruuteen). Kopioitujen olioitten paikalle laitetaan edelleenohjausosoite (forwarding pointer) eli osoite, joka osoittaa olion uuteen kopioon. Sitten lähdetään kulkemaan kohdeavaruutta olioittain ja kopioidaan lähdeavaruudesta kohdeavaruuteen kaikki ne oliot, joiden osoitteisiin näin törmätään ja joita ei ole vielä kopioitu. Joka tapauksessa korjataan kaikki osoitteet osoittamaan kohdeavaruuteen. Lopulta kaikki on kopioitu, ja voidaan taas jatkaa muuta työtä. Pysäytä ja kopioi -menetelmällä on samat perusedut ja -viat kuin merkkaa ja lakaise -menetelmällä. Erojakin tosin on. Koska kaikki olioiden luonti voidaan tehdä pinodynaamiseen tapaan, on se nopeaa. Kopiointi estää muistin pirstoutumisen (fragmentation). Jos ohjelma varaa paljon kekodynaamisia olioita, joista suurinta osaa tarvitaan hyvin lyhyen aikaa, on pysäytä ja kopioi ehdottomasti tehokkain tapa toteuttaa tämä (tehokkaampi jopa kuin pinodynaamisten olioiden käyttö). 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äily- 6

vä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 3 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ä. 2.2.4 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. http://www.hpl.hp.com/personal/hans_boehm/gc/ 7

2.3 Olio-ohjelmoinnin oliokäsite Olio-ohjelmoinnin olioihin (viittaan näihin alempana lyhyemmällä termillä OOolio) 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. 2.4 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 (yleisiä) olioita käyttäväksi. 3 Muuttuja Muuttuja on paikka, jossa voi säilyttää yhtä ja vain yhtä arvoa. Imperatiivisessa ohjelmoinnissa on oleellista, että muuttujan sisältämä arvo voidaan korvata toisella. Toisin kuin arvo, muuttuja on ajassa ja paikassa kiinni: muuttuja syntyy joskus ja kuolee joskus, ja se sijaitsee aina jossain paikassa. Muuttuja on muistialueen abstraktio. Elinaikanaan muuttuja on sidottu (engl. bound) johonkin tiettyyn muistialueeseen. Muistialueella on osoite ja pituus, kumpikin tavallisesti etumerkittömiä kokonaislukuja. Muuttujalla sen sijaan on yleensä nimi (nimettömät muuttujat jätämme toistaiseksi huomiotta) ja tyyppi, joista voidaan johtaa, mihin muistialueeseen muuttuja on sidottu. Muuttujalla on monia ominaisuuksia (engl. attributes). Tärkeimmät niistä on jo mainittu: nimi, tyyppi, arvo, muuttujan muistialueen osoite ja muuttujan muistialueen pituus. Nämä jakautuvat siististi kahteen eri lokeroon: muuttujan ominaisuus voi olla joko staattinen (engl. static) tai dynaaminen (engl. dynamic). Dynaaminen ominaisuus on sellainen, joka vaihtelee eri suorituskertojen välillä; staattinen ominaisuus on sama suorituskerroista riippumatta. Dynaamisia ominaisuuksia ovat arvo sekä muuttujan muistialueen osoite ja pituus. Staattisia ominaisuuksia ovat nimi ja tyyppi. Muuttujan muistialueen pituus on yleensä määritettävissä muuttujan tyypistä. 8

Usein muuttuja samastetaan nimeensä. Tämä on yleensä järkevää, mutta tarkkana pitää olla, ettei synny sekaannuksia, kun sama nimi voi eri yhteyksissä tarkoittaa eri muuttujaa. 4 Ympäristö Jokaisella lauseella on ympäristö, joka yhdistää muuttujien nimet niihin muuttujien ominaisuuksiin, jotka ovat kyseisen lauseen kohdalla voimassa. Kuten muuttujien ominaisuudet, myös ympäristöt luokitellaan staattisiin ja dynaamisiin: staattiset ympäristöt sisältävät staattisia (pysyviä) ominaisuuksia ja dynaamiset ympäristöt sisältävät dynaamisia (muuttuvia) ominaisuuksia. 5 Sijoituslause Sijoituslause on imperatiivisen ohjelmoinnin ensimmäinen kulmakivi. Se on yleensä muotoa x e (tarkka ilmiasu vaihtelee kielittäin), ja sen tehtävänä on laskea lauseke e nykyisessä ympäristössä ja muuttaa sitten ympäristöä siten, että muuttujan x arvoksi tulee lausekkeen e arvo. 6 Peräkkäistys Peräkkäistys on toinen imperatiivsen ohjelmoinnin kulmakivi. Se on yleensä hieman piilossa, sillä se esiintyy yleensä ohjelmissa vain muunlaisten kontrollirakenteiden käyttämättä jättämisenä. Peräkkäistyksen tehtävänä on suorittaa jono lauseita peräjälkeen: s 1 ;... ; s n tarkoittaa, että s 1 suoritetaan tämän peräkkäistyslauseen alussa voimassa olevassa ympäristössä ja yleisesti s i suoritetaan lauseen s i 1 muokkaamassa ympäristössä; peräkkäistyslauseen lopussa voimassa on se ympäristö, joka on voimassa s n :n lopussa. 7 Tyyppi Tyyppi (engl. type) on muuttujan tai lausekkeen ominaisuus. Sillä on kolme tehtävää: se kertoo, minkä joukon alkio muuttujan tai lausekkeen arvo on; lisäksi se kertoo, miten se tavujono, joka muodostaa sen muistialueen, johon muuttuja 9

on sidottu, tulkitaan arvoksi; lausekkeen tyyppi kertoo myös, mitkä operaatiot ovat kyseiselle lausekkeelle sallittuja. Olioihin kohdistuu operaatioita. Operaatiot olettavat, että kohteena oleva olio on jotain tiettyä tyyppiä. Tyyppivirheellä (type error) tarkoitetaan sitä, että jonkin suoritettavan operaation kohteena on olio, jonka tyyppi ei ole se, mitä operaatio olettaa. Huomaamatta jäävät tyyppivirheet johtavat ohjelman sekoamiseen: koska olio on eri tyyppiä kuin oletetaan, sen tulkinta arvoksi on täysin päätön ja tuloksena on roskaa. Mikäli ohjelmointikieli (eli oikeasti sen määrittely) vaatii toteutukseltaan, että se diagnosoi (ilmoittaa käyttäjälle) kaikki tyyppivirheet, kieli on vahvasti tyypitetty (strongly typed). Muussa tapauksessa kieli on heikoisti tyypitetty (weakly typed). Joitakin kieliä voidaan myös verrata keskenään sen mukaan, mitä tyyppivirheitä ne vaativat diagnosoitavaksi. Esimerkiksi C ja C++ ovat heikosti tyypitettyjä (molemmissa on mahdollista kirjoittaa tyyppivirheellinen ohjelma, jonka tyyppivirhettä ei toteutus huomaa), mutta C++ on paljon vahvemmin tyypitetty kuin C. Java, Haskell ja Scheme ovat esimerkkejä vahvasti tyypitetyistä kielistä. Kielen toteutus voi diagnosoida tyyppivirheen joko ennen suoritusta tai suoritusaikana. Ensin mainitussa tapauksessa on kyse staattisesta tyyppitarkastuksesta (static typechecking), jälkimmäisessä dynaamisesta tyyppitarkastuksesta (dynamic typechecking). Staattiseen tyyppitarkastukseen palataan myöhemmin; nyt tarkastellaan dynaamista tyyppitarkastusta. Mikäli dynaamista tyyppitarkastusta halutaan, tulee olion sisällä olla tieto siitä, mitä tyyppiä se on. Oliokielissä (joissa tämä tieto tarvitaan jo metodien dynaamisen sidonnan toteuttamiseen) tämä toteutetaan jo aiemmin mainitulla luokkaoliolla, jonka osoite tallennetaan jokaisen tätä tyyppiä olevan olion alkuun. Tyyppitarkastus voidaan tehdä tarkastamalla, mihin luokkaolioon olion alussa on osoitin. Vastaava tekniikka toimii myös muissa kielissä. Dynaamisissa funktiokielissä (mm. Lisp), joissa tyyppejä on varsin vähän, on tapana käyttää oliona konesanaa, josta varataan muutama bitti tyyppitunnisteeksi (isommat oliot laatikoidaan). Joissakin kielissä on hyvin rikas tyyppijärjestelmä; palaamme näihin myöhemmin applikatiivisen ohjelmoinnin yhteydessä. Seuraavat tyypit ja niiden muunnelmat ovat yleisiä imperatiivisissa kielissä (käyttämäni nimet ovat enimmäkseen C-kielestä peräisin): int Kokonaisluku väliltä 2 n 1,..., 2 n 1 1, joka esitetään kahden komplementtina yhdessä konesanassa. (Tässä n on konesanan pituus bitteinä.) unsigned int Kokonaisluku väliltä 0,..., 2 n 1, joka esitetään etumerkittömänä yhdessä konesanassa. (Tässä n on konesanan pituus bitteinä.) byte Kokonaisluku väliltä 2 n 1,..., 2 n 1 1, joka esitetään kahden komple- 10

menttina yhdessä tavussa. (Tässä n on tavun pituus bitteinä, tavallisesti 8, jolloin lukualue on 128,..., 127.) unsigned byte Kokonaisluku väliltä 0,..., 2 n 1, joka esitetään etumerkittömänä yhdessä tavussa. (Tässä n on tavun pituus bitteinä, tavallisesti 8, jolloin lukualue on 0,..., 255.) float Liukuluku, joka esitetään tavallisesti IEEE 754 -standardin yksinkertaisen tarkkuuden liukulukuna neljän tavun tavujonossa. double Liukuluku, joka esitetään tavallisesti IEEE 754 -standardin kaksinkertaisen tarkkuuden liukulukuna kahdeksan tavun tavujonossa. T [] Taulukko, jonka alkiot ovat tyyppiä T. Useimmat tyyppijärjestelmät laskevat näistä intin, unsigned intin, byten sekä unsigned byten kokonaislukutyypeiksi (engl. integral types) (siltä osin kuin tukevat näitä tyyppejä). Kaikki tyyppijärjestelmät sallivat aritmeettisten lausekkeiden rakentamisen niin, että jokaisen alilausekkeen tyyppi on sama; tällöin kokonaisen aritmeettisen lausekkeen tyyppi on tuo tyyppi. Vastaavasti aritmeettisissa lausekkeissa on yleensä sallittu liukulukutyyppien (float ja double) käyttäminen, jos kaikilla alilausekkeilla on sama tyyppi. Tiukka tyyppijärjestelmä kieltää tyyppien sekakäytön. Monet varsinkin vanhemmat imperatiiviset ohjelmointikielet määrittelevät kaikenlaisia automaattisia tyypinmuunnoksia (engl. coercions) näiden tyyppien välille: aina silloin, kun tiukka tyyppijärjestelmä olisi kieltämässä jonkin lausekkeen, muunnoksia käyttävä tyyppijärjestelmä pyrkii korjaamaan lausekkeen tyypityksen lisäämällä siihen muunnosoperaatioita. Esimerkiksi C-kielessä [1] byte 4 voidaan muuttaa tarvittaessa int-tyypiksi, float voidaan muuttaa doubleksi ja mikä tahansa unsigned -tyyppi voidaan muuttaa vastaavaksi etumerkilliseksi tyypiksi, ja näitä muunnoksia voidaan tarvittaessa ketjuttaa. C-kielen vuoden 1999 versiossa myös bool voidaan muuttaa tarvittaessa unsigned byteksi. Automaattiset tyypinmuunnokset ovat käteviä, mutta niillä on myös kääntöpuolensa. Tyyppijärjestelmän yksi tehtävä on suojata ohjelmoijaa virheiltä, jotka johtuvat tyyppien sekoittamisesta; jos kieli tekee automaattisia tyypinmuunnoksia, se todennäköisesti tekee niitä myös tilanteissa, joissa ohjelmoija ei niitä odota. Erityisen ikävä voi olla esimerkiksi kokonaislukutyypin muuttuminen yllättäen liukuluvuksi, mikä saattaa vähentää laskentatarkkuutta, tai tämän muunnoksen tapahtumatta jääminen silloin, kun ohjelmoija sitä odottaa (esimerkiksi C-kielessä jakolasku 1 / 2 antaa tulokseksi 0, koska molemmat operandit ovat kokonaislukutyyppisiä; useampikin ohjelmoija on odottanut tuossa tilanteessa tapahtuvan automaattisen muunnoksen liukuluvuksi, jolloin tulos olisi 0.5). 4. C-kielessä oikeasti byte on nimeltään signed char ja unsigned byte on nimeltään unsigned char. 11

8 Taulukoista Taulukoiden (engl. arrays) hyväksyminen kieleen vaatii hieman tarkennusta edellä esitettyihin käsitteisiin. Taulukkoja tukevan kielen sallittuihin lausekkeisiin pitää totta kai lisätä indeksointi a[i]; tässä vaaditaan, että a:n tyyppi on T[] jollekin tyypille T ja että i:n tyyppi on jokin kokonaislukutyyppi. Tällöin indeksointilausekkeen tyyppi on T ja sen arvo on se arvo, joka on tallennettuna taulukon a kohtaan i. Taulukoiden kanssa nousee ensimmäistä kertaa esille ohjelmointikielen turvallisuuden ongelma. Taulukot ovat äärellisiä, joten herää kysymys, mitä tapahtuu, jos taulukon koko on 5, taulukon indeksit alkavat nollasta ja indeksinä käytetään vaikkapa 42:ta. Yksinkertaisinta olisi toki unohtaa koko ongelma ja jättää asiasta huolehtiminen kokonaan ohjelmoijan vastuulle. Näin toimivat (tavallisesti) ainakin C ja C++. Jos ohjelmoija kuitenkin vahingossa (tai tahallaan!) indeksoi taulukon ulkopuolelle, vähintäänkin lausekkeen arvo on arvaamaton; hyvällä onnella käyttöjärjestelmä tai prosessori huomaa tämän kielletyn indeksoinnin ja lopettaa ohjelman. Vastuullisempaa olisi luultavasti se, että kielen toteutus tarkistaa indeksoinnin kunnollisuuden juuri ennen itse indeksointioperaatiota eli tekee rajatarkastuksen (engl. bounds check); tämä tosin hidastaa ohjelman suoritusta. Tehtävä 1 Pascal-kielessä taulukon indeksirajat kuuluvat taulukon tyyppiin: VAR a : ARRAY [5..10] OF INTEGER määrittelee muuttujan a, joka on kokonaislukuja sisältävä taulukko, jossa on kuusi alkiota ja indeksi i on sallittu, jos 5 i 10 pätee. Indeksirajojen täytyy tietenkin olla vakioita, koska ne ovat osa a:n tyyppiä. Mitä hyötyjä ja mitä haittoja tästä on? Jos taulukon on tarkoitus olla hyödyllinen osa imperatiivista kieltä, pitää olla mahdollista päivittää taulukon yksittäisen alkion arvoa. Tämä tarkoittaa, että indeksointi pitää sallia myös sijoituslauseen vasemmalla puolella muuttujan paikalla. Yksinkertaisinta varmaankin olisi sallia sijoituslause, joka on muotoa a[i] e, missä a ja i ovat kuten yllä ja e on sopivantyyppinen lauseke. Yleisemmin kuitenkin voi olla järkevää sanoa, että sijoituslause on e e, missä e ja e ovat samantyyppisiä lausekkeita. Ei kuitenkaan ole järkevää sanoa, että e :n arvo sijoitetaan e:n arvoon. Christopher Strachey [2] ehdotti 1960-luvulla arvon käsitteen jakamista kahtia: lausekkeella on kaksi arvoa, vasen arvo (engl. left value) ja oikea arvo (engl. right value) 5. Lausekkeen oikea arvo on se, mitä on edellä kutsuttu pelkäksi arvok- 5. Nämä englanninkieliset termit lyhennetään nykyisin yleensä lvalue ja rvalue. 12

si. Muuttujalausekkeen vasen arvo kuvaa sen muistialueen alkuosoitetta, joka on tuohon muuttujaan sidottu, ja yleisemmin lausekkeen vasen arvo kertoo, mihin muistialueeseen lausekkeen oikea arvo on tallennettu. Nämä nimet tulevat tietenkin siitä, että lausekkeen vasenta arvoa käytetään, kun lauseke esiintyy sijoituslauseen vasemmalla puolella, ja oikeaa arvoa käytetään, kun lauseke esiintyy sijoituslauseen oikealla puolella. Edellä esitetty vasemman arvon määritelmä on kuitenkin turhan salliva. Jos jokaisella lausekkeella olisi vasen arvo, niin se olisi myös lukuvakiolla 42. Jos näin on, niin sijoituslauseen 42 2 tulisi olla sallittu. Mitä se tekisi? Itse asiassa joissakin FORTRAN-kielten toteutuksissa on todella ollut mahdollista muuttaa lukuvakion arvoa niin, että tuollaisen sijoituslauseen jälkeen jokainen viittaus lukuvakioon 42 tarkoittaisikin 2:a. Tämä ei ole yleensä kuitenkaan järkevää, joten tavallisesti sovitaan, että kaikilla lausekkeilla ei ole vasenta arvoa. Tarkastelemistamme lausekkeista vasen arvo on vain muuttujalausekkeella sekä indeksointilausekkeella a[i], jos a:lla on vasen arvo. Kun taulukkon alkioon voi sijoittaa, on indeksoinnin rajatarkastus yllättävänkin tärkeää. Esimerkiksi jos taulukkoon luetaan verkosta dataa, saattaa rajatarkastuksen puuttuminen tehdä ohjelmointivirheestä tietoturvaongelman. Kannattaa huomata, että koska T[] on tyyppi, jos T on tyyppi, niin taulukot voivat koostua taulukoista. Viitteet [1] International Organization for Standardization. Programming languages C, 1999. ISO/IEC 9899:1999. [2] Christopher Strachey. Fundamental concepts in programming languages. Higher-Order and Symbolic Computation, 13:11 49, 2000. Perustuu Stracheyn vuonna 1967 pitämiin luentoihin. 13