Aliohjelma

Aliohjelma (kutsutaan eri yhteyksissä myös termeillä proseduuri, funktio, metodi tai rutiini) on ohjelmoinnissa itsenäinen ohjelman osa, joka suorittaa tietyn toiminnon ja jota voidaan kutsua eri puolilta pääohjelmaa tai muista aliohjelmista. Aliohjelman suorituksen jälkeen ohjelman suoritus jatkuu kutsuvassa ohjelmassa aliohjelmakutsua seuraavasta lauseesta.  

Proseduraalisessa ohjelmointikielessä aliohjelmia kutsutaan funktioiksi tai proseduureiksi. Olio-ohjelmoinnissa olion funktioita nimitetään jäsenfunktioiksi tai metodeiksi.

Ohjelmoinnissa esiintyy usein tilanne, että samanlaista toimintoa tarvitaan ohjelman useassa eri kohdassa. Kirjoittamalla toiminto aliohjelmaksi voidaan se suorittaa useasta eri kohdasta. Näin säästetään työtä. Jos sama toiminto kirjoitettaisiin ohjelmaan yhä uudelleen, ohjelmakoodi pitenisi ja tulisi epäselväksi ja virheiden mahdollisuus kasvaisi. Mahdolliset korjaukset ohjelmakoodiin pitäisi tehdä useaan paikkaan. Aliohjelmat myös lisäävät ohjelman modulaarisuutta, koska ne mahdollistavat ohjelmakoodin jakamisen pienempiin itsenäisiin osiin ja parantavat siten ohjelmiston luettavuutta, testattavuutta ja ylläpidettävyyttä.

Aliohjelmaa voidaan kutsua useassa kohdassa ohjelmaa. Aliohjelman määrittelyn yhteydessä esitellään joukko aliohjelman parametreja tai argumentteja. Aliohjelmalla voi olla parametrien lisäksi paikallisia muuttujia, jotka näkyvät ja ovat käytettävissä vain aliohjelman alueella (). Aliohjelman parametrit voivat olla muuttujia tai viittauksia muuttujiin.

Aliohjelman kutsuun on eräissä suorittimissa tuki käskytasolla: konekielinen käsky tallentaa senhetkisen ohjelmalaskurin arvon pinoon ja hyppää toiseen kohtaan ohjelmaa. Kun aliohjelma päättyy, paluuosoite haetaan pinon päältä. Kaikki ohjelmointikielet eivät tue pinoa: FORTRAN 77 ei tukenut pinoa vaan funktioilla oli oma muistialueensa argumenteille ja datalle.

Järjestelmäkutsu ei ole suora funktiokutsu kuten sovelluksissa, vaan kutsun on ylitettävä ytimen ja käyttäjäavaruuden välinen jako.

Tilaa jonne osoite tallennetaan kutsutaan aliohjelman aktivaatiotietueeksi. Siihen tallennetaan lisäksi aliohjelman käyttämät paikalliset muuttujat. Jos aliohjelma kutsuu itseään useaan kertaan, eli toimii rekursiivisesti, aktivaatiotietueita on useampi pinon päällä.

Historia 
Varhainen kuvaus alirutiinikirjastolle on esitetty vuonna 1948 John von Neumannin ja Herman H. Goldstinen teoksessa Planning and Coding of Problems for an Electronic Computing Instrument. Maurice V. Wilkes David Wheelerin ja Stanley Gillin kanssa esitteli käytännössä toimivan ratkaisun vuonna 1951 teoksessa Preparation of Programs for Electronic Digital Computers.
Vuonna 1977 keskusteltiin proseduurikutsujen haitoista verrattuna goto-käskyjen käyttöön: PDP-11 oli yksi ensimmäisiä tietokoneita, joissa oli käskytuki pinoon työntämiseen alirutiinikutsussa.

Toimintaperiaate 
Suoritin käyttää hyppykäskyä (jmp), kutsukäskyä (call, jsr) tai tarkoitukseen varattua haarautumiskäskyä (esim. bl, bsr) siirtyäkseen suoritettavaan ohjelmakohtaan. Tätä varten suorittimelle on kerrottava suoritettavan aliohjelman osoite.
Kun suoritin siirtyy aliohjelmaan, paluuosoite sijoitetaan pinoon jotta suoritin palaa oikeaan kohtaan aliohjelman lopussa. Pinoon voidaan lisätä useampia paluuosoitteita sisäkkäisiä kutsuja varten. Paluuosoite riippuu paikasta, jossa aliohjelmaa kutsutaan, esimerkiksi jos sitä kutsutaan kahdesti peräkkäin eri parametreilla. Seuraavan suoritettavan käskyn osoite voidaan sijoittaa pinoon ennen aliohjelman kutsua, jolloin se palautetaan aliohjelmasta paluun jälkeen ja suoritus jatkuu seuraavasta käskystä.
Suorittimen arkkitehtuurista riippuu käskyjen toteutustapa ja käskyt voivat yhdistää suorituspaikan työntämisen pinoon sekä ehdottoman hyppykäskyn suorittamisen. Kohde annetaan kääntäjälle aliohjelman nimenä, josta kääntäjä päättelee varsinaisen osoitteen: osoite voidaan laskea myös suorituksen aikana, jolloin voidaan käyttää uudelleensijoitettavia aliohjelmia.

Aliohjelmakutsun yhteydessä suorittimen rekisterien tilat on talletettava pinoon jotta aliohjelma ei ylikirjoita säilytettävää tietoa sen suorituksen aikana. Suorittimessa on rajattu määrä rekistereitä, joita kaikki aliohjelmat voivat käyttää. Vastuu rekisterin tilan säilömisessä voi olla aliohjelman kutsujalla tai kutsuttavalla aliohjelmalla, joissa on molemmissa tapauksissa etunsa ja haittansa. Kutsukäytäntö määrittää tavan aliohjelmien kutsumiseen. Suoritin voi sisältää erillisen käskyn rekisterien säilömistä varten ("push" ja "pop") tai ne voivat jäädä ohjelmoijan vastuulle: esimerkiksi MIPS-arkkitehtuurissa ei ole käskyjä tätä varten. SPARC käyttää suurta määrää rekistereitä liukuvan ikkunan periaatteella, jolloin aliohjelma ei näe kaikkia rekistereitä.

Suorittimella ei ole tietoa aliohjelmalle välitettävien muuttujien tyypeistä vaan se on ohjelmallisesti käsiteltävää tietoa joko käännösvaiheessa tai ajonaikaisella tyyppitarkistuksella.

Esimerkki 
Esimerkki aliohjelmasta, joka saa parametriksi numeroarvon (int eli integer) nimeltä ika ja joka palauttaa boolean-arvon (true/false) sen mukaan, onko ika alle 18 vai ei.
 public Boolean onkoHenkiloAlaikainen(int ika) {
   if(ika<18) {
       return true;
    } else {
       return false;
    }
 }

Tämän jälkeen aliohjelmaa voitaisiin kutsua esimerkiksi seuraavasti:
 if(onkoHenkiloAlaikainen(12) == true) {
    tulosta "Henkilö on alaikäinen!";
 }

Olio-ohjelmoinnissa eräissä kieli aliohjelman (metodin) eteen kirjoitetaan halutun olion nimi seuraavasti:
 if(henkilo.onkoHenkiloAlaikainen(12) == true) {
    tulosta "Henkilö on alaikäinen!";
 }

Esittely ja määrittely 
Eräät ohjelmointikielet kuten C-kieli erottavat esittelyn () ja määrittelyn tai toteutuksen (). Esittely kertoo miten funktiota kutsutaan ilman tarvetta tietää toteutusta toisessa osassa ohjelmaa.
void swap(int *a, int *b); /* esittely */

void swap(int *a, int *b) /* määrittely */
{
   int t = *a;
   *a = *b;
   *b = t;
}

Muuttujat 
Aliohjelma voi käyttää paikallisia muuttujia, jotka ovat rekisterissä tai pinossa vain aliohjelman suorituksen aikana.

int laske(int a, int b)
{
    int d; /* <- d voidaan sijoittaa rekisteriin tai pinoon */
    d = a + b; 

    return d;
}

Aliohjelmat voivat myös nähdä globaaleja muuttujia ja muuttaa niitä, tai olio-ohjelmoinnissa metodit voivat käsitellä luokan jäsenmuuttujia. Näiden muuttujien saatavuus ja elinaika on eri kuin aliohjelman paikallisen muuttujan.

int d; /* globaali, säilytettävä ohjelman suorituksen ajan, 
        * näkyvissä kaikkialle ohjelmassa */
void laske(int a, int b)
{
    d = a + b;
}

Jäsenmuuttujan elinkaari on sama kuin olion ja sen näkyvyys voi olla rajattu, jolloin sitä voi käyttää vain jäsenmetodien avulla.
class Laske
{
private:
    int d; /* jäsenmuuttuja */
public:
    void laske(int a, int b)
    {
        d = a + b;
    }
    int arvo() { return d; }
    Laske() { d = 0; } /* konstruktori, joka vain alustaa jäsenmuuttujan arvon */
};

Paluuarvo 
Yleinen tapa palauttaa aliohjelmasta tietoa on sen paluuarvon avulla. Esimerkiksi aliohjelma voi palauttaa int -tyyppisen kokonaisluvun ja sen arvona luvun 42 seuraavasti:
int anna_luku()
{
    return 42;
}

Oletusarvot 
Eräissä kielissä voidaan antaa oletusarvot argumenteille, jolloin kutsuessa ei välttämättä tarvitse antaa arvoa jokaiselle argumentille.
Esimerkki, joka antaa kahden muuttujan tulon:
int kertolasku(int a, int b = 1) { return a * b; }

void main()
{
  int tulo;
  tulo = kertolasku(2); /* 2 * 1 = 2*/
  tulo = kertolasku(2, 1); /* 2 * 1 = 2 */
  tulo = kertolasku(2, 2); /* 2 * 2 = 4 */
}

Parametrien välitys 
Parametreja voidaan välittää kielestä riippuen kolmella tavalla aliohjelmalle: 
 arvo (call by value)
 osoitin
 viite (viittaus, referenssi) 

Osoittimen ja viitteen tapauksessa aliohjelma voi palauttaa tietoa annettuihin muuttujiin tai olioihin. Esimerkiksi funktio, joka laskee kaksi arvoa yhteen, palauttaa summan sekä merkitsee muuttujan todeksi mikäli jompikumpi parametri ylittää maksimin.
void summa(int a, int *b, bool &maksimi)
{
    maksimi = false;
    if (a > INT_MAX || *b > INT_MAX)
    {
        maksimi = true;
    }
    *b = a + *b;
}

/* kutsuminen */
int main()
{
    bool maks = false;
    int a = 1;
    int b = 2;

    /* ensimmäiselle parametrille välitetään arvo, toiselle muuttujan osoite ja kolmannelle viite */ 
    summa(a, &b, maks);
}

Parametrien määrä 
C-kieli ja sen johdannaiset tukevat muuttuvaa määrää parametreja ellipsis (...) määrittelyllä. Tällöin aliohjelmassa itsessään on oltava tuki muuttujaluettelon käsittelyyn. Tyypillinen tapaus on printf() -funktio, jonka määrittely on muotoa:
int printf(const char *format, ...)

Käyttö:
printf("Hei maailma\n");
printf("Numero: %d\n", 42);

Ylikuormitus ja ylikirjoittaminen 

Eräät ohjelmointikielet tukevat ylikuormittamista (vaihtoehtoisia kutsutapoja ja toteutuksia) sekä ylikirjoittamista (ks. virtuaalimetodi).
Ylikuormittamisessa samalle aliohjelmalle voi olla rinnakkaisia toteutuksia eri tietotyypeillä kuten neliöjuuren laskentaan:
float sqrt(float val);
double sqrt(double val);

Hyökkäykset 
Tietoturvan kiertävät haittaohjelmat pyrkivät muuttamaan kutsuttavaa aliohjelmaa suorittaakseen oman rutiininsa tai ne voivat muuttaa paluuosoitetta, jolloin aliohjelman paluussa suoritus siirtyy hyökkääjän omaan rutiiniin ().
ASLR muuttaa osoitteita, jotta haavoittuvuuksien hyödyntäminen on vaikeampaa.

Funktio kutsujen optimointi 
Funktion kutsumisen nopeuteen vaikuttaa suoritusaikainen yleiskustannus (), joka sisältää argumenttien välittämisen, haarautumisen aliohjelmiin ja palaaminen takaisin kutsuvaan ohjelmaan. Yleiskustannuksella tarkoitetaan tässä yhteydessä ylimääräistä taakka ja suorittamisen aikaista viivettä, joka aiheutuu huonosti optimoitujen funktio kutsujen takia. Merkittävin yksittäinen lähde objektiorientoituneissa kielissä yleiskustannusten nousulle lienee dynaaminen sidonta () metodikutsuissa. Usein yleiskustannus sisältää myös prosessorin rekisterien tallentamisen ja palauttamisen, sekä kutsurungon tallennus- ja vapautustoimintojen varauksen. Ohjelmointikielestä riippuen voi funktiokutsu sisältää myös automaattisen funktion testauksen paluukoodista tai poikkeusten käsittelystä, joka voi vaikuttaa ohjelman yleiskustannuksiin. Optimoitujen funktiokutsujen sivuvaikutuksien määrittäminen voi myös olla erittäin hankalaa toteaa Ricen olettamus () mukaan. Esimerkiksi jos funktiolla on riippuvuuksia toisista funktioista, jonka takia täytyy funktiota kutsua uudestaan voi optimoinnista aiheutua ongelmia, kuten ohjelman ajautuminen ikuiseen silmukkaan tai ohjelman rakenteesta voi tulla monimutkainen.

Inline-funktio 
 tarkoittaa funktiokutsun korvaamista suoraan sen toteuttamisella ohjelmakoodin kutsukohdassa. Tämä tarkoittaa sitä, että funktiota ei kutsuta erikseen, vaan funktion sisältämä koodi lisätään suoraan kutsun tilalle, jolloin vältetään ylimääräinen funktiokutsu ja siihen liittyvä suoritusaikaviive. Inline-funktioita käytetään yleensä silloin, kun funktiota kutsutaan usein ja sen suoritusaikaviive vaikuttaa merkittävästi ohjelman suorituskykyyn. Inline-funktio voi kuitenkin johtaa suurempaan koodikokoon, mikä voi vaikuttaa negatiivisesti kääntämiseen, käännöksen kokoon ja suoritusaikaiseen muistinkäyttöön.

Katso myös 
 Virtuaalimetodi
 Järjestelmäkutsu

Lähteet 

Ohjelmointi