Tutnew työkalu C++:n dynaamisen muistinhallinnan testaamiseen

Samankaltaiset tiedostot
4.2 Muistinhallintaa avustava kirjasto Tutnew

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Kääntäjän virheilmoituksia

Osoitin ja viittaus C++:ssa

Dynaaminen muisti. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät 2017.

C-ohjelmoinnin peruskurssi. Pasi Sarolahti

Ohjelmoinnin perusteet Y Python

C++11 lambdat: [](){} Matti Rintala

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

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

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

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

PERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki

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

Harjoitustyö: virtuaalikone

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

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

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

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

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

15. Ohjelmoinnin tekniikkaa 15.1

5. HelloWorld-ohjelma 5.1

Dynaaminen muisti Rakenteiset tietotyypit

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

Ohjelmointi 1 Taulukot ja merkkijonot

Tietueet. Tietueiden määrittely

Rajapinnat ja olioiden välittäminen

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

7. Oliot ja viitteet 7.1

C++ Ohjelmoijan käsikirja. Johdanto

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Tässä tehtävässä käsittelet metodeja, listoja sekä alkulukuja (englanniksi prime ).

14. Poikkeukset 14.1

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

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

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

11/20: Konepelti auki

4. Luokan testaus ja käyttö olion kautta 4.1

PRINCIPLES OF PROGRAMMING LANGUAGES - DEBUGGER

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

Taulukot. Jukka Harju, Jukka Juslin

14. Poikkeukset 14.1

D-OHJELMOINTIKIELI. AA-kerho, 33. Antti Uusimäki. Arto Savolainen

2 Konekieli, aliohjelmat, keskeytykset

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

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

Harjoitustyön testaus. Juha Taina

12. Näppäimistöltä lukeminen 12.1

Automaattinen yksikkötestaus

Muuttujien roolit Kiintoarvo cin >> r;

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

C-ohjelmoinnin peruskurssi. Pasi Sarolahti

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

ELM GROUP 04. Teemu Laakso Henrik Talarmo

TIE Tietorakenteet ja algoritmit 1. TIE Tietorakenteet ja algoritmit

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

15. Ohjelmoinnin tekniikkaa 15.1

Loppukurssin järjestelyt

Dart. Ryhmä 38. Ville Tahvanainen. Juha Häkli

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

Poikkeustenkäsittely

Ohjelmoinnin perusteet, syksy 2006

Ohjelmoinnin perusteet Y Python

Maastotietokannan torrent-jakelun shapefile-tiedostojen purkaminen zip-arkistoista Windows-komentojonoilla

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

Osoittimet ja taulukot

Java-kielen perusteet

Loppukurssin järjestelyt C:n edistyneet piirteet

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {

5. HelloWorld-ohjelma 5.1

Apuja ohjelmointiin» Yleisiä virheitä

Tietorakenteet ja algoritmit syksy Laskuharjoitus 1

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

TT00AA Ohjelmoinnin jatko (TT10S1ECD)

TAMPEREEN TEKNILLINEN YLIOPISTO Digitaali- ja tietokonetekniikan laitos. Harjoitustyö 4: Cache, osa 2

Ohjelmoinnin perusteet Y Python

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

Ohjelmointi 1. Kumppanit

7. Näytölle tulostaminen 7.1

Lyhyt kertaus osoittimista

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Osa III. Olioiden luominen vapaalle muistialueelle

Rakenteiset tietotyypit Moniulotteiset taulukot

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

Harjoitus 5 (viikko 48)

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

TAMPEREEN TEKNILLINEN YLIOPISTO

JReleaser Yksikkötestaus ja JUnit. Mikko Mäkelä

Ohjelmoinnin jatkokurssi, kurssikoe

Harjoitustyö 3 - Millosemeni

15. oppitunti. Taulukot. Osa. Mikä on taulukko?

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Olio-ohjelmointi Poikkeusten käsittelystä. 1. Johdanto

ITKP102 Ohjelmointi 1 (6 op)

Transkriptio:

Tutnew työkalu C++:n dynaamisen muistinhallinnan testaamiseen Matti Rintala (matti.rintala@iki.fi) 29. lokakuuta 2007 1 Johdanto C++ on teollisuudessa erittäin suosittu ohjelmointikieli ja siitä johtuen sitä käytetään myös opetuskielenä suuressa osassa oppilaitoksia. C++ on erittäin tehokas ja varsin monikäyttöinen ohjelmointikieli, mutta sitä ei ole suunniteltu opetuskäyttöön. Tästä johtuen kieli sisältää monia ansoja ja vaaroja, jotka aiheuttavat ylimääräistä päänvaivaa erityisesti aloittelevalle ohjelmoijalle, jolla usein ei ole käsitystä siitä, mistä ja miten ohjelmassa olevaa virhettä kannattaisi etsiä. Toinen ongelma ohjelmoinnin opetuksessa on, että opetuksessa yleensä korostetaan, että ohjelmien tulisi pystyä reagoimaan järkevästi muistin loppumiseen. Tätä on kuitenkin erittäin vaikea testata, koska nykyisissä tietokoneissa muistia on niin paljon, että sen loppuminen on erittäin epätodennäköistä ja usein vaikea saada aikaan. Tutnew on Tampereen teknillisellä korkeakoululla käytössä oleva kirjasto, joka auttaa ohjelmoijaa C++:n dynaamisen muistinhallinnan virheiden huomaamisessa ja paikantamisessa. Lisäksi Tutnew antaa mahdollisuuden simuloida koneen muistin loppumista sekä saada tilastotietoa dynaamisen muistin käytöstä. Tutnew ei pyri korjaamaan muistinhallinan virheitä, vaan mahdollisuuksien mukaan antamaan ohjelmoijalle selkeitä virheilmoituksia, jotka kertovat mitä ja missä on mennyt pieleen. Tutnew pyrkii lisäämään mahdollisuutta, että muistinhallinnan kannalta virheellinen ohjelma toimii virheellisesti ja kaatuu mahdollisimman pian muistinhallintavirheen jälkeen. Koska Tutnew on tarkoitettu myös ohjelmoinnin alkeisopetukseen, on sen käyttö mahdollisimman läpinäkyvää ja helppoa. Käyttääkseen Tutnew tä ohjelmoijan täytyy vain lisätä yksi #include-komento jokaiseen kooditiedostoonsa sekä käskeä kääntäjää linkittämään Tutnew-kirjasto mukaan suoritettavaan ohjelmaan. Tutnew-kirjastoa on käytetty ja testattu TTKK:lla muutaman vuoden ajan ohjelmoinnin perusopetuksessa, jossa siitä on saatu hyviä tuloksia. Tutnew, sen dokumentaatio ja muuta informaatiota on saatavilla sivulta http://www.iki.fi/ ~bitti/tutnew/ siitä voi kysellä myös tämän artikkelin kirjoittajalta (matti.rintala@iki.fi). 2 C++ ja dynaaminen muistinhallinta Ohjelmoijan kannalta C++-kielessä muistinhallinta jakautuu kahteen osaan. Kaikkien nimettyjen muuttujien sekä kääntäjän itsensä automaattisesti tuottamien väliaikaisolioiden elinkaari määräytyy ohjelman rakenteesta ja ne tuhotaan automaattisesti. Sen sijaan dynaamisesti new-operaattorilla luotujen olioiden elinkaari ei ole käännösaikaisesti määrätty, ja ohjelmoijan täytyy tuhota ne erikseen operaattorilla delete jossain ohjelman vaiheessa. Jos dynaamisesti varattua muistia ei vapauteta missään vaiheessa, tapahtuu muistivuoto. C++-standardi takaa kuitenkin, että kaikki ohjelman dynaamisesti varaama muisti vapautetaan, kun ohjelman suoritus päättyy. Tämän vuoksi dynaamisen muistin vuotoihin ei aina suhtauduta kovin vakavasti. 1 Tämä artikkeli julkaisun [Rintala, 2002] uudelleentaitettu versio. 1

Muistivuodot ovat kuitenkin vakava asia ainakin kahdesta syystä. Jos ohjelman suoritus kestää kauan, muistivuodot kuluttavat koneen muistiresursseja. Tämä saattaa aiheuttaa joko muistin ennenaikaisen loppumisen tai ohjelman hidastumisen, kun virtuaalimuistijärjestelmä alkaa käyttää kiintolevyä muistin korvikkeena. Toinen muistivuotojen vaikutus on, että vaikka ohjelman dynaamisesti varaama muisti vapautetaankin ohjelman lopussa, ei kyseiseen muistiin luotujen olioiden purkaja-jäsenfunktioita (engl. destructor) suoriteta. Purkajat puolestaan saattavat sisältää tärkeää toiminnallisuutta kuten muiden resurssien vapautuksia, tiedon päivittämistä kiintolevylle tai tietokantoihin yms. Tämän vuoksi muistivuodot saattavat aiheuttaa muita resurssivuotoja tai ohjelman virheellisen käyttäytymisen. Tyypillisiä dynaamisen muistinhallinnan virheitä ovat myös muistin vapauttaminen useaan kertaan ja muistialueen käyttäminen vielä sen vapauttamisen jälkeen. Näiden virheiden vaikutukset ohjelman toimintaan ovat C++-kielessä määrittelemättömiä. Tyypillisesti ne aiheuttavat joko ohjelman suorituksen keskeytymisen enemmän tai vähemmän selkeään virheilmoitukseen tai ohjelman muistin sotkeentumisen, mikä saattaa aiheuttaa ohjelman virheellisen käyttäytymisen. Monissa ohjelmointikielissä dynaamisen muistinhallinnan virheitä on pyritty estämään erilaisten muistin roskienkeruujärjestelmien (garbage collection) avulla. Nämä vapauttavat ohjelmoijan enemmän tai vähemmän dynaamisen muistinhallinnan suunnittelusta, mikä tietysti helpottaa erityisesti ohjelmoinnin alkeiden opiskelua. C++ ei kuitenkaan sisällä muistin roskienkeruuta. Kielen kehittäjät ovat ilmoittaneet syyksi tähän sen, että C++ on kehitetty erityisesti ohjelmien tehokkuutta silmällä pitäen [Stroustrup, 1994, luku 10.7]. Lisäksi automaattinen roskienkeruu sopii huonosti tilanteisiin, joissa olioiden tuhoutumishetkellä on väliä esimerkiksi sen takia, että olion siivoustoimenpiteet pitää suorittaa tiettyyn aikaan. Tämä on mahdollista, jos siivoustoimenpiteet sisältävät muiden resurssien vapautuksia, luku/tulostus-operaatioita tai käyttöliittymään liittyviä toimenpiteitä [Rintala ja Jokinen, 2000, luku ]. Ohjelman dynaamisessa muistinhallinnassa olevat virheet ovat erityisen hankalia paikantaa. Dynaamisen muistin vapauttamatta jättäminen aiheuttaa muistivuotoja, jotka jäävät yleensä huomaamatta, koska ohjelman muisti vapautetaan kuitenkin ohjelman päättyessä. Niinpä muistivuodot ovat varsin tavallisia jopa tuotantokäytössä olevissa ohjelmissa. Vapautetun muistin käyttäminen vapautuksen jälkeen aiheuttaa myös vaikeita virheitä. Näissä tapauksissa ohjelma saattaa toimia näennäisesti oikein pitkäänkin, kunnes jossain vaiheessa ohjelma ottaa jo vapautetun muistialueen uudelleen käyttöön. Tällöin sama muistialue kuuluu näennäisesti yhtaikaa sekä jo vapautettuun että uuteen olioon, jolloin muistiin yhden olion kautta tehdyt muutokset sotkevat toisen olion muistin. Tällaiset virheet eivät välttämättä edes esiinny joka kerralla, koska muistialueiden uudelleenkäyttämiseen saattavat vaikuttaa koneen fyysisen muistin määrä sekä jopa se, paljonko muita ohjelmia koneessa on käynnissä. Dynaamisen muistinhallinnan testaamiseen on olemassa useita kaupallisia työkaluja, mutta ne ovat yleensä varsin kalliita ja toimivat vain tietyissä käyttöjärjestelmissä ja kääntäjissä. Lisäksi tällaisten työkalujen käyttö vaatii usein sen verran C++-kielen ja ohjelmien testauksen ymmärtämistä, että ne sopivat huonosti ohjelmoinnin alkeisopetukseen. Tutnew n käyttö Tutnew n käyttö on varsin yksinkertaista. Normaalisti siihen liittyy vain kaksi asiaa: Ohjelmassa jokaisen dynaamista muistia käyttävän kooditiedoston tulee lukea sisään Tutnew n otsikkotiedosto. Tämä tapahtuu komennolla #include <tutnew>. Tämän komennon tulee olla tiedostossa heti kaikkien muiden #include-komentojen jälkeen. 1 Lopullista ajettavaa ohjelmabinaaria linkatessa tulee ohjelmaan linkata mukaan Tutnew n kirjasto. Esimerkiksi GCC:n tapauksessa tämä tapahtuu lisäämällä linkityskomennon loppuun optio -ltutnew, jos Tutnew on asennettu systeemikirjastoksi. 1 Syynä tähän vaatimukseen on, että Tutnew määrittelee avainsanat new ja delete makroiksi. 2

Edellä mainittuja asioita lukuunottamatta ohjelmaan ei yleensä tarvitse tehdä mitään muutoksia. Listaus 1 sisältää kaksi esimerkkitiedostoa sekä komennot niiden kääntämiseen GCC:llä (käännöskomennoissa on myös mukana muita käännösoptioita, jotka tekevät lisätarkistuksia ohjelmasta). Kun ohjelma on käännetty, valvoo Tutnew automaattisesti ohjelman dynaamista muistinhallintaa ja virheen sattuessa antaa virheilmoituksen. Tutnew n toimintaan voi vaikuttaa käyttäjän valinnan mukaan joko ympäristömuuttujien tai esikääntäjäsymbolien avulla, mutta niitä ei käsitellä tarkemmin tässä artikkelissa. Tutnew on toteutettu C++:n kirjastona, joka on kirjoitettu kielen nykyisen standardin ISO 14882 [ISO, 1998] mukaisesti. Se käyttää dynaamisen muistinhallinan testaamiseen vain kielen omia mekanismeja. Niinpä Tutnew pitäisi olla mahdollista kääntää mihin tahansa riittävän uuteen kääntäjäympäristöön käyttöjärjestelmästä ja kääntäjän valmistajasta riippumatta. Tutnew n kehitys on tehty GCC-kääntäjällä [GNU, 2001a], ja se toimii ainakin kääntäjän versioilla 2.95.2, 2.95. ja.0. Käännös- ja testausympäristöinä ovat olleet Redhat Linux ja Sun Microsystemsin Solariksen eri versiot. Näissä ympäristöissä Tutnew on toiminut erinomaisesti. Tutnew n aikaisempia versioita on käytetty myös Windows-ympäristössä djgpp- ja Borlandkääntäjissä. Uudempia versioita ei enää ole testattu noissa ympäristöissä, mutta tiedossa ei ole mitään syytä, miksei Tutnew toimisi niissäkin ympäristöissä. Artikkelin kirjoittaja ottaa mielellään vastaan kokemuksia Tutnew stä eri ympäristöissä. 4 Tutnew n havaitsemat virheet Seuraavassa on lueteltu kaikki ne virheet, jotka Tutnew pystyy joko havaitsemaan ja raportoimaan tai joiden sattuessa Tutnew pyrkii C++-kielen rajoissa sotkemaan ohjelman toiminnan niin paljon, että virhe ilmenisi mahdollisimman aikaisessa vaiheessa. Tämän artikkelin esimerkeissä Tutnew on konfiguroitu niin, että sen virheilmoitukset tulostuvat suomen kielellä. Oletusarvoisesti Tutnew n tulostuskieli on englanti. Lisäksi kunkin ohjelmaesimerkin koodin oletetaan sijaitsevan tiedostossa nimeltä virheet.cc. 4.1 Muistivuodot Muistivuoto tapahtuu, kun ohjelmoija varaa ohjelmassa muistia dynaamisesti new llä mutta ei vapauta sitä. Tällaiset virheet Tutnew pystyy havaitsemaan ja raportoimaan luonnollisesti vasta ohjelman lopussa. Kun Tutnew havaitsee ohjelman lopussa muistivuodon, se raportoi vapauttamatta jätetyn muistialueen koon sekä sen, millä rivillä ja missä tiedostossa kyseinen muistialue varattiin.................................................... esimerkki.cc................................................... 1 #include <iostream> 2 #include <string> #include "varaus.hh" 4 #include <tutnew> 5 6 int main() 7 { 8 string s("testi"); 9 char* teksti = varaa teksti(s); 10 std::cout << teksti << std::endl; 11 vapauta teksti(teksti); 12 }................................................. Käännöskomennot................................................. g++ -Wall -W -pedantic -ansi -c esimerkki.cc g++ -Wall -W -pedantic -ansi -o esimerkki esimerkki.o -ltutnew LISTAUS 1: Esimerkki Tutnew n käytöstä

Ikävä kyllä Tutnew ei kykene ilmoittamaan sitä, minkä tyyppinen olio muistialueella on aikanaan sijainnut. Useimmissa ohjelmissa yhdellä koodirivillä on kuitenkin vain yksi new-komento, joten rivinumero yksilöi vuodon yksikäsitteisesti. Listauksessa 2 on lyhyt ohjelma, jossa on muistivuoto. Kun tämä ohjelma käännetään ja linkataan Tutnew n kanssa, ohjelma tulostaa ajettaessa seuraavaa: Loppu Tutnew: seuraavia muistilohkoja ei ole vapautettu: Tutnew: 4 tavu(a) varattu new llä rivillä 11 tiedostossa virheet.cc Vaikka tässä esimerkissä Tutnew n virheilmoitus riittää muistivuodon löytämiseen, on todellisissa ohjelmissa tilanne usein hankalampi. Sama new-komento voidaan suorittaa ohjelmassa useita kertoja, joten virheilmoitus ei vielä yksin kerro, milloin muistivuodon aiheuttanut muistialue on varattu eikä sitä, missä se olisi pitänyt vapauttaa. 4.2 Muistin vapauttaminen useamman kerran Tyypillinen vastareaktio muistivuotojen vaaraan on, että ohjelmoija vapauttaa osoittimien päässä olevaa muistia suunnittelematta ohjelman muistinhallintaa mitenkään erityisesti. Tällöin tavallinen virhe on, että sama muistialue vapautetaan useassa ohjelman kohdassa eri osoittimia käyttäen. Tutnew pystyy havaitsemaan virheellisen tuhoamisyrityksen ja antamaan siitä virheilmoituksen heti sen sattuessa. Virheilmoitus kertoo, missä muistialue on varattu, missä se on vapautettu ensimmäisen kerran ja missä sitä yritetään vapauttaa uudelleen. Tällaisessa tilanteessa Tutnew normaalisti keskeyttää ohjelman suorituksen, mutta tähän voi ohjelmoija itse vaikuttaa. Listaus näyttää listauksen 2 ohjelman korjattuna niin, että aiemmin vapauttamatta jätetty muisti yritetään vapauttaa kahteen kertaan. Ohjelman tulostus on seuraava: Tutnew: delete - yritetty uudelleenvapauttaa muistia rivillä 15 tiedostossa virheet.cc. (muisti varattu new llä rivillä 12 tiedostossa virheet.cc ja vapautettu deletellä rivillä 7 tiedostossa virheet.cc) 4. Muistin käyttö tuhoamisen jälkeen Useaan kertaan vapauttamisen lisäksi yleinen muistinhallintaongelma on, että dynaamisesti varattu muisti vapautetaan liian aikaisin ja sitä käytetään vielä vapauttamisen jälkeen (tämä ongelma esiintyy myös staattisesti varatun muistin yhteydessä, mutta sille Tutnew ei kykene 1 #include <iostream> 2 #include <tutnew> 4 void tulosta(int* p) 5 { 6 std::cout << *p << std::endl; 7 } 8 9 int main() 10 { 11 int* p = new int; 12 *p = ; 1 tulosta(p); 14 std::cout << "Loppu" << std::endl; 15 return 0; 16 } LISTAUS 2: Muistivuoto 4

1 #include <iostream> 2 #include <tutnew> 4 void tulosta(int* p) 5 { 6 std::cout << *p << std::endl; 7 delete p; 8 } 9 10 int main() 11 { 12 int* p = new int; 1 *p = ; 14 tulosta(p); 15 delete p; 16 std::cout << "Loppu" << std::endl; 17 return 0; 18 } LISTAUS : Muistin vapauttaminen kahdesti tekemään mitään). Koska Tutnew on vain kirjasto, sen mahdollisuudet havaita jo vapautettuun muistiin tapahtuvat viittaukset ovat rajalliset. Kun Tutnew tä käyttävä ohjelma vapauttaa dynaamisesti varaamansa muistin, Tutnew ei todellisuudessa vapauta kyseistä muistialuetta vaan täyttää sen testibittikuviolla ja lisää sen omaan tarkkailukirjanpitoonsa. Tämä auttaa jo vapautetun muistin käytön havaitsemiseen seuraavasti: Jos ohjelma lukee vapautettua muistialuetta, se ei enää löydä sieltä itse kirjoittamaansa dataa vaan Tutnew n testikuvion. 2 Tällöin ohjelma todennäköisesti toimii väärin jo testivaiheessa, ja virhe voidaan etsiä ja korjata. Ohjelman lopussa Tutnew käy läpi kaikki vapautetut muistialueet ja tarkastaa, että testikuvio on ehjä. Jos ohjelma on kirjoittanut jo vapautettuun muistialueeseen, muuttaa kirjoitus erittäin todennäköisesti testikuviota. Tällöin Tutnew huomaa muutoksen ja antaa virheilmoituksen. Valitettavasti molemmissa edellisissä tapauksissa Tutnew kykenee vain edesauttamaan virheen olemassaolon havaitsemista. Sen sijaan virheen aiheuttaneen lukemisen tai kirjoittamisen sijainnista ohjelmassa Tutnew ei pysty kertomaan mitään, vaan virhe on paikannettava perinteisin testauskeinoin. Listauksen 4 ohjelma vapauttaa varaamansa muistin jo rivillä 9, mutta lukee muistialueen sisällön vielä rivillä 10 ja muuttaa muistialuetta rivillä 11. Ohjelma tulostaa ajettaessa seuraavaa: 5-84281097 Tutnew: vapautettu muisti sotkettu vapautuksen jälkeen: Tutnew: 4 tavu(a) varattu new llä rivillä 6 tiedostossa virheet.cc ja vapautettu rivillä 9 tiedostossa virheet.cc Kuten tulostuksesta näkee, aiempi muistialueen sisältö 5 on vapautuksen yhteydessä muuttunut selvästi virheelliseksi arvoksi -84281097 (tämä arvo voi vaihdella ajokerrasta toiseen). Lisäksi Tutnew raportoi muistin virheellisestä muuttamisesta ja kertoo, missä muisti on varattu ja vapautettu. Vapautetun muistin tarkastaminen lisää ohjelman todellista muistinkulutusta, koska Tutnew ei todellisuudessa voi vapauttaa muistia. Lisäksi testikuvioiden tuottaminen ja tutkiminen ohjelman lopussa hidastaa ohjelmaa. Vapautetun muistin käytön testin voi myös halutessaan kytkeä pois päältä. 2 Tämä muistialueen sotkeminen on täysin C++-standardin mukaista, koska vapautettuun muistialueeseen viittaaminen aiheuttaa joka tapauksessa ohjelman määrittelemättömän toiminnon. 5

1 #include <iostream> 2 #include <tutnew> 4 int main() 5 { 6 int* p = new int; 7 *p = 5; 8 std::cout << *p << std::endl; 9 delete p; 10 std::cout << *p << std::endl; 11 *p = 8; 12 return 0; 1 } LISTAUS 4: Muistin käyttäminen vapautuksen jälkeen 4.4 Alustamattoman muistin käyttäminen Tyypillisesti monissa ympäristöissä käyttöjärjestelmä alustaa varatun dynaamisen muistin alussa täyteen nollatavuja. Tätä C++-standardi ei kuitenkaan takaa, vaan varatun muistin sisältö on alussa määrittelemätön. C++:ssa perustyyppisiä muuttujia ei automaattisesti alusteta mihinkään arvoon, ja lisäksi nollaa täynnä oleva bittikuvio vastaa useimmissa konearkkitehtuureissa niin kokonaislukua 0, liukulukua 0.0 ja tyhjää NULL-osoitinta. Niinpä alustamattomat muuttujat C++:ssa saavat usein sattumalta arvokseen nolla, mikä tekee niiden havaitsemisen hankalaksi. Tutnew pyrkii helpottamaan alustamattomien muuttujien löytymistä täyttämällä kaikki varatut muistialueet alussa samalla testikuviolla, jota se käyttää vapautetun muistin sotkemiseen. Tämän seurauksena dynaamisessa muistissa olevat alustamattomat muuttujat eivät saakaan arvokseen nollaa, vaan jonkin siitä poikkeavan (todennäköisesti hyvin suuren) arvon. Tämä aiheuttaa suurella todennäköisyydellä ohjelman toiminnassa virheitä, joten virhettä tiedetään etsiä. Samoin kuin vapautetun muistin sotkemisessa Tutnew voi vain pyrkiä saamaan alustamattomia muuttujia käyttävän ohjelman toimimaan väärin. Se ei pysty antamaan tietoa siitä, missä alustaminen on unohtunut. Samoin Tutnew pystyy vaikuttamaan vain dynaamisessa muistissa oleviin alustamattomiin muuttujiin. 4.5 Olemattoman muistin vapauttaminen C++:ssa taulukoita on tyypillistä käydä läpi osoitinaritmetiikkaa käyttäen. Tällöin saattaa käydä niin, että dynaamisesti varattu taulukko yritetään vapauttaa käyttäen osoitinta, joka ei enää osoitakaan taulukon alkuun. Tällöin ohjelman toiminta on määrittelemätön. Samanlainen virhe saattaa syntyä, jos deletelle annetaan alustamaton osoitin tai osoitin, jonka sisältö on aiemman muistinhallintavirheen vuoksi sotkeentunut. Kolmas tyypillinen tilanne on, että deletellä yritetään osoittimen kautta vapauttaa muuttujaa, joka ei ole new llä varattu. Tutnew tarkastaa aina, että deletelle annettu osoitin todella osoittaa jonkin dynaamisesti varatun muistialueen alkuun. Mikäli näin ei ole, antaa Tutnew virheilmoituksen, kuten listauksen 5 tulostus osoittaa: Tutnew: delete[] - yritetty vapauttaa varaamatonta muistia rivillä 7 tiedostossa virheet.cc. 4.6 Dynaamisen taulukon indeksointivirheet Taulukoiden yli- tai ali-indeksointi on tyypillinen ohjelmointivirhe C++:ssa (ja muissakin ohjelmointikielissä). Ikävä kyllä C++-standardi ei määrittele, mitä tapahtuu jos taulukkoa indeksoidaan väärin. Tyypilliset C++ kääntäjät tuottavat koodia, jossa ohjelma yksinkertaisesti lukee 6

1 #include <tutnew> 2 int main() 4 { 5 int* p = new int[10]; 6 *(++p) = 5; // p ei enää osoita alkuun 7 delete[ ] p; 8 return 0; 9 } LISTAUS 5: Varaamattoman muistin vapauttaminen muistia taulukon muistialueen jommalta kummalta puolelta. Samanlainen virhe tapahtuu, jos taulukkoa käydään läpi osoitinaritmetiikan avulla, ja osoitin pääsee liikkumaan taulukon ulkopuolelle. Koska nämä virheet eivät suoranaisesti liity dynaamiseen muistinhallintaan, ei Tutnew pysty huomaamaan niitä kovin hyvin. Tutnew pyrkii kuitenkin helpottamaan indeksointivirheiden löytämistä varaamalla pyydetyn muistialueen molemmille puolelle pienen puskurialueen muistia. Tutnew täyttää tämän alueen jo varauksen yhteydessä testikuviolla. Kuten vapautetun muistin käytössäkin, tästä on kaksi hyötyä: Jos ohjelma virheellisesti viittaa taulukon ulkopuolelle indeksointivirheen tai osoitinaritmetiikan takia, ei luettu arvo ainakaan ole nolla vaan testikuvion tuottamaa puppua. Tutnew tarkastaa testikuvion eheyden muistin vapauttamisen ja ohjelman loppumisen yhteydessä. Jos ohjelma on kirjoittanut taulukon ulkopuolelle ja sotkenut testikuvion, antaa Tutnew virheilmoituksen. Seuraavassa on listauksessa 6 näkyvän ohjelman tulostus. Siitä näkyy puskurialueen ja testikuvion vaikutus sekä lukemisen että kirjoittamisen yhteydessä: -66052 Tutnew: delete[] - muisti sotkeentunut varatun muistilohkon ympärillä. (muisti varattu new[] llä rivillä 6 tiedostossa virheet.cc delete[] tapahtui rivillä 11 tiedostossa virheet.cc) Yli- ja ali-indeksoinnin havaitsemisessa on sama ikävä puoli kuin vapautetun muistin käytössä Tutnew ei pysty kertomaan, missä päin ohjelmaa virhe on sattunut. Lisäksi on huomattava, että Tutnew n käyttämä puskurialue ei ole kovin suuri (oletusarvoisesti 16 tavua muistialueen molemmilla puolilla), joten suuret yli- ja ali-indeksoinnit jäävät Tutnew ltä huomaamatta. 1 #include <iostream> 2 #include <tutnew> 4 int main() 5 { 6 int* p = new int[10]; 7 int* q = p; 8 p[5] = ; // Ok 9 std::cout << p[10] << std::endl; // Virhe 10 p[10] = 8; // Virhe 11 delete[ ] p; 12 *(--q) = 8; // Tuplavirhe 1 return 0; 14 } LISTAUS 6: Dynaamisen taulukon yli-indeksointi 7

4.7 Väärä tuhoamistapa C++ vaatii, että dynaamisesti varatut yksittäiset oliot pitää tuhota komennolla delete ja dynaamisesti varatut taulukot komennolla delete[ ]. Ikävä kyllä kääntäjä ei pysty varmistamaan, että näin todella tehdään, koska dynaamisesti varattua taulukkoa käsitellään täsmälleen samantyyppisen osoittimen avulla kuin yksittäistäkin oliota. Mikäli edellämainittua C++:n sääntöä rikotaan, on ohjelman toiminta määrittelemätön. Tyypillinen virhe on taulukon vapauttaminen tavallisella deletellä. Useissa kääntäjissä C++:n perustyyppien tapauksessa virhe ei aiheuta ongelmia, mutta ohjelmoijan omien tyyppien tapauksessa virheellinen tuhoaminen aiheuttaa erilaisia ongelmia, kuten purkajien kutsumatta jättämisiä tai ohjelman kaatumisen. Tutnew tarkastaa muistin vapauttamisen yhteydessä että käytetty delete on oikein tyyppinen ja antaa virheilmoituksen, jos näin ei ole. Listaus 7 ja sen tulostus ovat esimerkkinä tästä: Tutnew: delete - yritetty vapauttaa muistia rivillä 10 tiedostossa virheet.cc, muisti varattu taulukko-new[] llä rivillä 5 tiedostossa virheet.cc. 5 Tilastotietojen tulostaminen Tutnew kerää tilastoa kaikista muistinvarauksista ja vapauttamisista. Tämän tilastotiedon voi tulostaa joko itse ohjelmasta käsin funktiokutsulla tai ohjelman lopussa sopivan esikääntäjäsymbolin tai ympäristömuuttujan määrittelemällä. Listauksessa 8 on lyhyt ohjelma, jonka päättyessä Tutnew:n muistitilasto näyttää seuraavalta: Tutnew: Käyttäjän tilastotietoja: Tutnew: onnistuneiden new-operaatioiden lukumäärä: 10 Tutnew: onnistuneiden new[]-operaatioiden lukumäärä: 1 Tutnew: epäonnistuneiden new-operaatioiden lukumäärä: 0 Tutnew: epäonnistuneiden new[]-operaatioiden lukumäärä: 0 Tutnew: delete-operaatioiden lukumäärä: 10 Tutnew: delete[]-operaatioiden lukumäärä: 1 Tutnew: varatun muistin kokonaismäärä: 80 tavu(a) Tutnew: vapautetun muistin kokonaismäärä: 80 tavu(a) Tutnew: varatun muistin maksimimäärä: 80 tavu(a) Tutnew: varatun muistin tämänhetkinen määrä: 0 tavu(a) Tilastotulostuksessaan Tutnew kertoo sekä onnistuneiden että epäonnistuneiden muistinvarausten lukumäärän (varaus voi epäonnistua esimerkiksi muistin loppumisen simuloimisen yhteydessä) sekä muistin vapautusten lukumäärät. Esimerkin tapauksessa yhteenlaskettu 80 tavua on varattu ohjelman riveillä 6 ja 14. Lisäksi tulostuksesta käy ilmi kaikkien muistinvarausten yhteenlaskettu muistimäärä ja sama vapautuksille. Tutnew pitää kirjaa myös ohjelman ajon 1 #include <tutnew> 2 int main() 4 { 5 char* p = new char[20]; // Varataan taulukko 6 for (unsigned int i = 0; i < 20; ++i) 7 { 8 p[i] = static cast<char>( A + i); 9 } 10 delete p; // Tuhotaan yksittäisenä 11 return 0; 12 } LISTAUS 7: Väärä new n ja deleten yhdistäminen 8

1 #include <vector> 2 #include <tutnew> 4 int main() 5 { 6 int* t = new int[10]; 7 for (unsigned int i = 0; i < 10; ++i) 8 { 9 t[i] = i; 10 } 11 std::vector<int*> v; 12 for (unsigned int i = 0; i < 10; ++i) 1 { 14 v.push back(new int); // Loppuun uusi alkio 15 *v[i] = t[i]; 16 } 17 for (unsigned int i = 0; i < 10; ++i) 18 { 19 delete v.back(); v.back() = 0; 20 v.pop back(); 21 } 22 delete[ ] t; 2 } LISTAUS 8: Useita muistivarauksia tekevä ohjelma aikana kerrallaan varattuna olleen muistin maksimimäärästä ja senhetkisestä varatun muistin määrästä. Jos käytetyn kääntäjän kirjastot ovat standardin mukaisia, tulostaa Tutnew myös tilaston kirjastojen (ja muiden Tutnew tä käyttämättömien ohjelman osien) dynaamisesta muistinkulutuksesta erikseen. Näitä lukuja ei ole sisällytetty käyttäjän tilastoon, vaan se sisältää ainoastaan ne muistinvaraukset, jotka on tehty Tutnew tä käyttävissä kooditiedostoissa. Valitettavasti nykyiset GCC:n (2.95.x ja.0) kirjastot perustuvat Silicon Graphicsin STL-toteutukseen [SGI, 2001], jonka muistinvaraus on epästandardi, eikä Tutnew näin ollen voi pitää siitä tilastoa. Tämä vika tullaan ilmeisesti korjaamaan tulevissa versioissa. [GNU, 2001b] 6 Muistin loppumisen simulointi Lähes kaikkialla ohjelmoinnin perusopetuksessa korostetaan, että dynaamista muistia varattaessa tulee varautua siihen, että muistia ei saadakaan varattua. Usein muistin loppumiseen varautuminen jää kuitenkin teorian asteelle. Ohjelman testaaminen tämän virhetyypin osalta on hankalaa, koska nykyisissä tietokoneissa on yleensä niin paljon muistia (sekä fyysistä muistia että virtuaalimuistia), että sen kuluttaminen loppuun on vaikeaa. Lisäksi muistin todella loppuessa myös testaustyökalujen yms. käyttö voi olla vaikeaa. Tämän vuoksi Tutnew tarjoaa mahdollisuuden rajoittaa ohjelman käytössä olevaa dynaamisen muistin määrää ja näin simuloida muistin loppumista. Muistin loppumiseen reagoimiseen löytyy ohjeita esim. kirjasta Effective C++ [Meyers, 1998, Item 7]. 6.1 Dynaamisen muistin määrän rajoittaminen Ehkä tavallisin tapa simuloida muistin loppumista Tutnew ssä on rajoittaa ohjelman käytössä olevan dynaamisen muistin määrä sopivaan pieneen arvoon. Tämän voi tehdä joko ympäristömuuttujalla, käännösaikana tai kesken ohjelman suorituksen ohjelmasta käsin. Rajoituksen voi 9

kytkeä joko vain Tutnew n alaisuudessa tehdyille muistinvarauksille tai koko ohjelman dynaamiselle muistille. Kun muisti loppuu, tulostaa Tutnew oletusarvoisesti varoituksen, jossa se kertoo, minkä new-operaation muistinvaraus epäonnistui. Lisäksi Tutnew tulostaa testauksen helpottamiseksi muistitilaston (ks. kohta 5). Tämän jälkeen ohjelman suoritus jatkuu C++:n sääntöjen mukaan ikään kuin koneen muisti olisi loppunut. Jos varoitusta muistin loppumisesta ei haluta, sen voi kytkeä pois päältä. Listaus 9 sisältää koodin, jonka muistin loppumiseen varautumista halutaan testata. Kun testiympäristössä dynaamisen muistin määrä rajoitettiin yhteen kilotavuun, tulosti ohjelma seuraavaa: Tutnew: varoitus - muisti loppui new ssä rivillä 15 tiedostossa virheet.cc Tutnew: Käyttäjän tilastotietoja: Tutnew: onnistuneiden new-operaatioiden lukumäärä: Tutnew: onnistuneiden new[]-operaatioiden lukumäärä: 1 Tutnew: epäonnistuneiden new-operaatioiden lukumäärä: 1 Tutnew: epäonnistuneiden new[]-operaatioiden lukumäärä: 0 Tutnew: delete-operaatioiden lukumäärä: 0 Tutnew: delete[]-operaatioiden lukumäärä: 0 Tutnew: varatun muistin kokonaismäärä: 1024 tavu(a) Tutnew: vapautetun muistin kokonaismäärä: 0 tavu(a) Tutnew: varatun muistin maksimimäärä: 1024 tavu(a) Tutnew: varatun muistin tämänhetkinen määrä: 1024 tavu(a) Muisti loppui! Tutnew: seuraavia muistilohkoja ei ole vapautettu: Tutnew: 1000 tavu(a) varattu new[] llä rivillä 12 tiedostossa virheet.cc Ikävä kyllä Tutnew pystyy jälleen rajoittamaan vain C++:n new- ja operator new -operaattoreilla varatun muistin määrää, joten muistin loppumista GCC:n versioiden 2.95.x ja.0 STL:n sisällä ei voi simuloida. 1 #include <iostream> 2 #include <cstddef> #include <new> 4 #include <tutnew> 5 6 // HUOM! Tämän ohjelman muistin loppumisen käsittely on virheellinen! 7 int main() 8 { 9 double** p = 0; 10 try 11 { 12 p = new double*[250]; 1 for (unsigned int i=0; i<250; ++i) 14 { 15 p[i] = new double(i); 16 } 17 } 18 catch (std::bad alloc&) 19 { 20 std::cerr << "Muisti loppui!" << std::endl; 21 return EXIT FAILURE; 22 } 2 24 for (unsigned int i=0; i<250; ++i) 25 { 26 delete p[i]; p[i] = 0; 27 } 28 delete[ ] p; 29 return EXIT SUCCESS; 0 } LISTAUS 9: Ohjelma muistin loppumisen simulointiin 10

Tutnew: 8 tavu(a) varattu new llä rivillä 15 tiedostossa virheet.cc Tutnew: 8 tavu(a) varattu new llä rivillä 15 tiedostossa virheet.cc Tutnew: 8 tavu(a) varattu new llä rivillä 15 tiedostossa virheet.cc Kuten tulostuksesta näkee, muistin loppuminen aiheutti ohjelmaan muistivuodon. Ohjelman korjaaminen toimivaksi on mainio harjoitustehtävä sellaiselle lukijalle, joka ei ole perehtynyt paljoa virhekäsittelyn koodaamiseen. 6.2 Muistin loppuminen varausten määrän perusteella Joskus saattaa tulla tarve testata muistin loppumisen vaikutusta juuri tietyssä vaiheessa ohjelmaa. Tällöin voi olla vaikea laskea, kuinka paljon ohjelmalle on sallittava muistia, jotta se loppuisi juuri oikeassa kohtaa. Tutnew ssä on mahdollista asettaa raja myös sille, kuinka monen muistinvarauksen jälkeen muistin loppumista simuloidaan. Rajoitus kytketään päälle aiempien rajoitusten tapaan joko ympäristömuuttujalla, esikääntäjäsymbolilla tai funktiokutsulla. 6. Todennäköisyyteen perustuva muistin loppuminen Tutnew antaa myös mahdollisuuden simuloida muistin loppumista satunnaislukugeneraattorin avulla. Tällä tavoin on mahdollista testata muistin loppumista satunnaisessa ohjelman kohdassa. Muistin loppumisen todennäköisyys on mahdollista asettaa aiempien rajoitusten tapaan joko ympäristömuuttujalla, esikääntäjäsymbolilla tai funktiokutsulla. Arvona näille annetaan liukuluku väliltä 0.0 1.0. Arvo 0.0 tarkoittaa, että todennäköisyyssimulointia ei käytetä, 1.0 taas että muisti loppuu joka varauksella varmasti. Täten esim. arvo 0.1 tarkoittaa, että muisti loppuu satunnaisesti keskimäärin joka kymmenennellä varauksella. Jotta tämä simulointi olisi toistettavissa, käyttää Tutnew joka ohjelman ajokerralla samoja valesatunnaislukuja, joten muisti loppuu joka kerralla samoissa kohdissa. Käytettäviä valesatunnaislukuja voi vaihtaa asettamalla satunnaislukugeneraattorin siemenluku halutuksi. Tutnew käyttää valesatunnaislukujen tuottamiseen omaa valesatunnaislukugeneraattoriaan, joten tämän ominaisuuden käyttö ei sotke minkään kirjaston tuottamia valesatunnaislukuja. 6.4 Poikkeusten vuotaminen muistin loppuessa Muistin loppumisesta aiheutuva C++:n poikkeus on tietysti syytä ottaa kiinni ohjelmassa. Jos näin ei tehdä, kutsuu ohjelma funktiota std::terminate() (tai sille funktiolla std::set terminate() määriteltyä korviketta). Tämän funktion täytyy keskeyttää ohjelman suoritus. Tutnew valvoo, että muistin loppumisesta aiheutuva poikkeus todella otetaan kiinni. Jos näin ei käy, eikä ohjelma ole määritellyt omaa korvikettaan funktiolle std::terminate(), tulostaa Tutnew virheilmoituksen. Tutnew tarkkailee myös käyttäjän std::terminate():lle mahdollisesti määrittelemää korviketta ja valvoo, että se tosiaan lopettaa ohjelman suorituksen. Jos näin ei käy, seuraa jälleen virheilmoitus. Samalla tavoin Tutnew käyttäytyy, jos muistin loppumisesta aiheutuva poikkeus ei pääse vuotamaan funktiosta ulos funktion poikkeusmääreen (exception specification) vuoksi. Tällöin ohjelma kutsuu funktiota std::unexpected() tai sille funktiolla std::set unexpected() annettua korviketta. Tutnew tarkkailee näiden käyttäytymistä samoin kuin edellä std::terminate():n tapauksessa. 6.5 Muistin loppuminen ja new_handler Ohjelma voi rekisteröidä oman funktionsa, jota kutsutaan ennen poikkeuksen heittämistä, jos dynaaminen muisti loppuu. Tämä funktio rekisteröidään funktiolla std::set new handler(), ja se voi esim. vapauttaa käyttöön lisää muistia tms. Tämä toimii edelleen Tutnew n tapauksessakin. Myös siinä tapauksessa, että muistin loppumista vain simuloidaan, kutsutaan käyttäjän rekisteröimää funktiota kuten pitääkin. 11

7 Kokemuksia Tutnew n käytöstä Tutnew-kirjastoa on käytetty Tampereen teknillisen korkeakoulun kurssien Ohjelmointi 1, Ohjelmointi 2, Laaja ohjelmointi ja Olio-ohjelmointi harjoitustöissä menestyksellä jo vuodesta 1997 alkaen. Tulokset sen käytöstä ovat olleet lähes yksinomaan positiivisia. Koska Tutnew n käyttäminen on helppoa, se ei ole tuottanut juurikaan vaikeuksia edes ohjelmoinnin alkeita opetteleville opiskelijoille. Tutnew on vähentänyt palautetuissa harjoitustöissä havaittujen muistivuotojen määrää oleellisesti. Tärkein hyöty Tutnew stä on kuitenkin ollut, että opiskelijat itse pääsevät testaamaan ohjelmansa muistinkäyttöä. Tällöin suurena apuna on ollut erityisesti se, että Tutnew kertoo jokaisesti vapauttamatta jääneestä muistilohkosta sen, millä rivillä ja missä tiedostossa se on varattu. Samoin hyödylliseksi on koettu se, että muistin kahteen kertaan vapauttamisesta saa virheilmoituksen, josta käy ilmi, missä muisti on alunperin vapautettu. Ehkä yllättävänäkin hyötynä Tutnew n käytöstä on ollut alustamattomien tietorakenteiden paljastuminen (kohta 4.4). Tämä ominaisuus lisättiin Tutnew hyn jälkeenpäin vain siksi, että se oli helppo toteuttaa. Se on kuitenkin osoittautunut yllättävän tarpeelliseksi ohjelmoinnin peruskursseilla, joissa dynaamisten tietorakenteiden käyttöä vasta opetellaan. Suurimpana vaikeutena Tutnew n käytössä on koettu se, että komennon #include <tutnew> täytyy olla kooditiedostoissa viimeisenä #include-komentona (ks. kohta ). Tämän noudattamatta jättämisestä seuraavista virheilmoituksista on aloittelevan ohjelmoijan vaikea päätellä, mikä on mennyt pieleen. Tätä on pyritty estämään sillä, että TTKK:lla käytössä oleva C++-tyylianalysaattori Style++ [Uimonen, 2000] varoittaa, jos Tutnew n #include puuttuu tai on väärässä paikassa. Toinen ongelmakohta on ollut, että ohjelmaa linkitettäessä ohjelmoijan täytyy muistaa linkittää mukaan myös Tutnew n kirjastot. Aloittelijoiden on toisaalta ollut hankalaa muistaa tämä, mutta toisaalta he ovat samalla oppineet valmiiden kirjastojen käyttöä käytännössä. Syksystä 2001 saakka TTKK:lla on ollut myös käytössä GCC -pohjainen kääntäjätyökalu Tutg++, joka (muiden ominaisuuksiensa lisäksi) käyttää Tutnew tä automaattisesti ilman, että ohjelmoijan täytyy tehdä mitään erityistä. Tutg++:n käyttö on vähentänyt Tutnew n käyttöön liittyviä ongelmia oleellisesti. Viitteet [GNU, 2001a] Free Software Foundation. The GCC Home Page, heinäkuu 2001. http://gcc. gnu.org/. [GNU, 2001b] Free Software Foundation. Gnatsweb (GCC bug report page), elokuu 2001. http: //gcc.gnu.org/cgi-bin/gnatsweb.pl, Problem Report #904. [ISO, 1998] ISO/IEC. International Standard 14882 Programming Languages C++, syyskuu 1998. [Meyers, 1996] Scott Meyers. More Effective C++. Addison-Wesley, 1996. ISBN 0-201-671-X. Löytyy myös elektronisessa muodossa osana teosta [Meyers, 1999]. [Meyers, 1998] Scott Meyers. Effective C++ 2nd edition. Addison-Wesley, 1998. ISBN 0-201- 92488-9. Löytyy myös elektronisessa muodossa osana teosta [Meyers, 1999]. [Meyers, 1999] Scott Meyers. Effective C++ CD. Addison-Wesley, 1999. ISBN 0-201-1015-5. Sisältää teokset [Meyers, 1998] ja [Meyers, 1996] elektronisessa muodossa. [Rintala ja Jokinen, 2000] Matti Rintala ja Jyke Jokinen. Olioiden ohjelmointi C++:lla. Satku- Kauppakaari, 2000. ISBN 952-14-069-1. [Rintala, 2002] Matti Rintala. Tutnew työkalu C++:n dynaamisen muistinhallinnan testaamiseen. Tietojenkäsittelytiede, (17):8 2, toukokuu 2002. 12

[SGI, 2001] Silicon Graphics, Inc. Standard Template Library Programmer s Guide, heinäkuu 2001. http://www.sgi.com/tech/stl/. [Stroustrup, 1994] Bjarne Stroustrup. The Design and Evolution of C++. Addison-Wesley, 1994. ISBN 0-201-540-. [Uimonen, 2000] Toni Uimonen. Tyylin automaattinen arviointi ohjelmistoissa. Diplomityö, Tampereen teknillinen korkeakoulu, Ohjelmistotekniikan laitos, 2000. 1