Ylös Edellinen Seuraava Otsikkosivu Hakemisto Sisällys

8.6.4 Parametrinvälitysmekanismi

Ainoa C- kielen tuntema parametrinvälitysmekanismi on parametrien välittäminen arvoina. Tämä tarkoittaa sitä, että aliohjelma saa käyttöönsä vain (luku)arvoja, ei muuta. Olkoon meillä esimerkiksi ongelmana tehdä aliohjelma, jolle viedään parametreinä tunnit ja minuutit sekä niihin lisättävä minuuttimäärä. Jos ensimmäinen yritys olisi seuraava:

c-muut\aikalisa.cpp - yritys lisätä arvoja

	#include <iostream.h>
	void lisaa(int h, int m, int lisa_min) 	:-(
	{
	  int yht_min = h*60 + m + lisa_min;
	  h = yht_min / 60;
	  m = yht_min % 60;
	}
	
	void tulosta(int h, int m)
	{
	  cout << h << ":" << m << endl;
	}
	
	int main(void)
	{
	  int h=12,m=15;
	  lisaa(h,m,55);
	  tulosta(h,m);
	  return 0;
	}

Tämä ei tietenkään toimisi! Hyvä kääntäjä jopa varoittaisi että:

	Warn :  aikalisa.cpp(8,2):'m' is assigned a value that is never used
	Warn :  aikalisa.cpp(7,2):'h' is assigned a value that is never used

Mutta miksi ohjelma ei toimisi? Seuraavan selityksen voi ehkä ohittaa ensimmäisellä lukukerralla. Tutkitaanpa tarkemmin mitä aliohjelmakutsussa oikein tapahtuu. Oikaisemme seuraavassa hieman joissakin kohdissa liian tekniikan kiertämiseksi, mutta emme kovin paljoa. Katsotaanpa ensin miten kääntäjä kääntäisi aliohjelmakutsun ( Borland C++ 5.1, 32-bittinen käännös, rekisterimuuttujat kielletty jottei optimointi tekisi konekielisestä ohjelmasta liian monimutkaista):

	lisaa(h,m,55);
	
	muistiosoite assembler         selitys
	-------------------------------------------------------------------------
	004010F9     push 0x37           pinoon 55
	004010FB     push [ebp-0x08]     pinoon m:n arvo 
	004010FE     push [ebp-0x04]     pinoon h:n arvo
	00401101     call lisaa          mennään aliohjelmaan lisää
	00401106     add esp,0x0c        poistetaan pinosta 12 tavua (3 x int)

Kun saavutaan aliohjelmaan lisaa, on pino siis seuraavan näköinen:

	muistiosoite  sisältö          selitys
	------------------------------------------------------------------------
	064FDEC       00401106 <-ESP   paluuosoite kun aliohjelma on suoritettu
	064FDF0       0000000C         h:n arvo, eli 12
	064FDF4       0000000F         m:n arvo, eli 15
	064FDF8       00000037         lisa_min, eli 55

Eli aliohjelmaan saavuttaessa aliohjelmalla on käytössään vain arvot 12,15 ja 55. Näitä se käyttää tässä järjestyksessä omien parametriensä arvoina, eli m,h,lisa_min.

Muutetaanpa ohjelmaan parametrin välitys osoitteiden avulla:

c-muut\aikalis2.cpp - parametrin välitys osoittimilla

	#include <iostream.h>
	
	void lisaa(int *ph, int *pm, int lisa_min)
	{
	  int yht_min = *ph * 60 + *pm + lisa_min;
	  *ph = yht_min / 60;
	  *pm = yht_min % 60;
	}
	
	void tulosta(int h, int m)
	{
	  cout << h << ":" << m << endl;
	}
	
	int main(void)
	{
	  int h=12,m=15;
	  lisaa(&h,&m,55);
	  tulosta(h,m);
	  return 0;
	}

Nyt ohjelma toimii ja tulostaa 13:10 kuten pitääkin. Eikä kääntäjäkään anna varoituksia. Mitä nyt tapahtuu ohjelman sisällä? Aliohjelmakutsusta seuraa:

	lisaa(&h,&m,55);
	
	muistiosoite assembler         selitys
	-------------------------------------------------------------------------
	0040111D     push 0x37           pinoon 55
	0040111F     lea eax,[ebp-0x08]  m:osoite rekisteriin eax
	00401122     push eax            ja tämä pinoon (0064FDFC) 
	00401123     lea edx,[ebp-0x04]  h:n osoite rekisteriin edx
	00401126     push edx            ja tämä pinoon (0064FE00)
	00401127     call lisaa          mennään aliohjelmaan lisää
	0040112C     add esp,0x0c        poistetaan pinosta 12 tavua (2 x int)

Pino on aliohjelmaan saavuttaessa seuraavan näköinen

	muistiosoite assembler         selitys
	------------------------------------------------------------------------
	0064FDEC      0040112C <-ESP   paluuosoite kun aliohjelma on suoritettu
	0064FDF0      0064FE00         h:n osoite,  eli ph:n arvo
	0064FDF4      0064FDFC         m:n osoite,  eli pm:n arvo
	0064FDF8      00000037         lisa_min, eli 55

Aliohjelman alussa olevien lauseiden

	muistiosoite assembler         selitys
	-------------------------------------------------------------------------
	0040107C     push ebp            pinoon talteen rekisterin ebp arvo
	0040107D     mov  ebp,esp        ebp:hn pinon pinnan osoite
	0040107F     push ecx            pinoon tilaa yhdelle kokonaisluvulle (min)

suorittamisen jälkeen pino näyttää seuraavalta

	muistiosoite  sisältö          selitys
	------------------------------------------------------------------------
	0064FDE4  -04 00000000 <-ESP   aliohjelman oma tila, eli min-muuttuja
	0064FDE8  +00 0064FE04 <-EBP   vanha EBP:n arvo, johon EBP nyt osoittaa
	0064FDEC  +04 0040112C         paluuosoite kun aliohjelma on suoritettu
	0064FDF0  +08 0064FE00         h:n osoite,  eli ph:n arvo
	0064FDF4  +0C 0064FDFC         m:n osoite,  eli pm:n arvo
	0064FDF8  +10 00000037         lisa_min, eli 55

Esimerkiksi minuuteille sijoitus kääntyisi seuraavasti:

	*pm = yht_min % 60;
	
	muistiosoite assembler         selitys
	-------------------------------------------------------------------------
	004010A1     mov  eax,[ebp-0x04] eax:ään yht_min muuttujan arvo        
	004010A4     mov  ecx,0x0000003c ecx:ään 60 jakajaksi          
	004010A9     cdq                 konvertoidaan eax 64 bitiksi edx:eax      
	004010AA     idiv ecx            jaetaan edx:eax/ecx:llä tulos eax, jakoj. edx
	004010AC     mov  eax,[ebp+0x0c] eax:ään m:n osoite (eli pm:n arvo)
	004010AF     mov  [eax],edx      sinne muistipaikkaan, jonne eax asoittaa,
	                                 eli 0064FDFC, eli pääohjelman m:ään kopioidaan
	                                 edx, eli 10.  HUOM!  Pääohjelman m:n arvo
	                                 muuttui juuri tällä hetkellä!
	}                                aliohjelmasta poistuminen
	004010B1     pop  ecx            pinosta pois aliohjelman omat muuttujat
	004010B2     pop  ebp            alkuperäinen ebp:n arvo talteen
	004010B3     ret                 ja paluu osoitteeseen joko on pinon päällä nyt
	                                 eli 0040112C, eli pääohjelmaan call-lauseen
	                                 jälkeen

HUOM! Kääntäjä ei ole optimoivillakaan asetuksilla huomannut, että sekä h:n että m:n sijoitus saataisiin samasta jakolaskusta, koska toisessa tarvitaan kokonaisosaa ja toisessa jakojäännöstä, jotka molemmat saadaan samalla kertaa idiv operaatiossa. Huonoa kääntäjän kannalta, mutta ilahduttavaa että hyvälle assembler- ohjelmoijallekin jää vielä käyttöä.

Takaisin asiaan. Nyt siis aliohjelmalla oli pelkkien 12,15,55 arvojen sijasta käytössä osoitteet pääohjelman h:hon ja m:ään sekä arvo 55. Näin aliohjelma pystyi muuttamaan kutsuneen ohjelman muuttujien arvoja. Sama kuvana ennen *pm- sijoitusta:

Kuva 8.3 Sijoitus aliohjelmasta kutsuvan ohjelman muuttujaan

Tehtävä 8.73 aikalis3.cpp

Kirjoita aikalis2.cpp:stä viitemuuttujia käyttävä versio. Sisäisesti ohjelma kääntyy täsmälleen samanlaiseksi kuin osoittimilla, eli osoittimet ja viitemuuttujat ovat sisäisesti todellakin sama asia.

Tehtävä 8.74 Muotoilu?

Kokeilepa lisätä aikaan esimerkiksi 50 min. Mitä tulostuu? Miten vian voisi korjata?

Tehtävä 8.75 Tiedon lukeminen

Kirjoita aliohjelma lue, joka kysyy ja lukee arvon kellonajalle, syöttö muodossa 12:15.


Ylös Edellinen Seuraava Otsikkosivu Hakemisto Sisällys