#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 usedMutta 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 55Eli aliohjelmaan saavuttaessa aliohjelmalla on käytössään vain arvot 12,15 ja 55. Näitä se käyttää tässä järjestyksessä omien parametriensa arvoina, eli m,h,lisa_min.
Muutetaanpa ohjelmaan parametrin välitys osoitteiden avulla:
#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 55Aliohjelman 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 55Esimerkiksi 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älkeenHUOM! 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: