torstai 28. elokuuta 2008

Avainsana static C:ssä.





avainsana static on ehkä C:n (ja myös C++:n) huonoimmin suunniteltu ominaisuus. Ja kun puhutaan C:n huonoimmin suunnitellusta osasta, puhumme ehkä maailmankaikkeuden kaikkien ohjelmointikielien sekavimmasta avainsanasta :)

Syyt ovat edelliseen väittämään ovat hyvin yksinkertaisia. Ensinnäkin C:ssä ja C++:ssa static määrettä käytetään joissain määrin eri tavoin. (C++:ssa static sanalle tulee vielä pari uutta käyttötarkoitusta). Tämä on omiaan sekoittamnaan jo ennestään sekavan koodarin pään. Kun tähän vielä lisätään se, että C:ssä sanalla static on kaksi toisistaan näennäisesti täysin poikkeavaa käyttötarkoitusta, hiipii mieleen ajatus että kielen kehittäjien sanavarasto lienee ollut hieman vajaa... Ehkä olisi ollut syytä harkita kahden eri sanan käyttämistä...

No kieli on tehty jo ajat sitten, joten tilannetta on turha itkeä enää. Täytyy siis vain yrittää oppia. Katsotaan ensin yksinkertaisempi, mutta äärimmäisen käytännöllinen static:n käyttötarkoitus. Kaikessa kauneudessaan:

Laittamalla sana static globaalin muuttujan määrittelyn eteen, muuttujan näkyvyys rajoittuu tiedostoon jossa se on esitelty.
Samalla tavalla laitettaessa static määre funktion esittelyn eteen, saadaan funktion näkyvyys rajoitettua ainoastaan siihen tiedostoon, jossa se on toteutettu.

esim:

funktiot.h

extern int G_globaali1;
extern int G_globaali2;


funktiot.c

int G_globaali1=1;
static int G_globaali2=2;
static void funktio1();
void funktio2();

void funktio1()
{
...
}

void funktio2()
{
...
}


main.c

#include "funktiot.h"
int main()
{
int x;
x=G_globaali1;
x=G_globaali2;
funktio1();
funktio2();
return 0;
}


Ylläoleva esimerkki ei linkkantuisi, sillä funktio1():tä ja G_globaali2:ta ei löytyisi main.c tiedostossa. Voitte kokeilla tätä tekemällä esimerkin mukaiset tiedostot, ja laittamalla jonkinlaisen toteutuksen funktio1:lle ja funktio2:lle. gcc:llä homma onnistuu sitten komennolla

gcc -o testi main.c funktiot.c


Koettakaa sitten ottaa static sanat pois, ja kääntäkää uudestaan...

Miksi kukaan sitten haluaisi rajoittaa funktion/muuttujan näkyvyyttä tällä tavalla? Pohditaanpa hetki. Minä olen ollut töissä eräässä projektissa, jossa samaa mammuttimaisen suurta ohjelmistoa teki arviolta 200 henkeä. En edes tuntenut kaikkia, saati että olisin tuntenut kaikkien tekemien ohjelmistokomponenttien toteutuksen. Ja ajan oloon unohdin myös itse tekemieni koodien yksityiskohdat.

ohjelmisto pohjautui viestipohjaiseen reaaliaika käyttöjärjestelmään, ja vain arvata saattaa kuinka moni koodari toteutti toisistaan tietämättä esim. send_msg() viestin. Testikoodeja ohjelmaan oli satojatuhansia rivejä, ja yksittäisi testejä tuhansia. Sopii vain pohtia kuinka monta status tai G_status tai G_test_status globaalia koodeissa esiteltiin. Yksinkertainen keino välttää päällekkäiset määritykset oli käyttää juuri static avainsanaa.

No niin. Koska tämä näyttää ihan selkeältä ja järkevältä, niin on syytä sekoittaa pakkaa ettei totuus unohtuisi :) Eihän C:n näin helppoa kuulu olla, joten vedetään kehiin static sanan käyttö funktion sisäisten muuttujien esittelyssä. No nythän näkyvyyttä ei ole tarpeen rajoittaa, funktion sisäiset muuttujathan eivät muutenkaan näy funktion ulkopuolelle. Paikallisten muuttujien yhteydessä static tekeekin muuttujista "staattisia". Ts. ne ovat varattuna jo ohjelman käynnistyksestä lähtien, riippumatta siitä, tuleeko ohjelman suoritus koskaan kyseistä funktiota kutsumaan. Staattiset muuttujat myös säilyttävät arvonsa funktion kutsujen välissä.

Ja valoitetaan jälleen esimerkillä:


typedef enum ElaskurinToimintamoodi
{
ElaskurinToimintamoodi_lisaa=0,
ElaskurinToimintamoodi_vahenna,
ElaskurinToimintamoodi_palauta
}ElaskurinToimintamoodi;

int main()
{
while(laskuri(ElaskurinToimintamoodi_palauta)<10)
{
if(laskuri(ElaskurinToimintamoodi_lisaa)==0)
{
printf("virhe laskurissa");
return -1;
};
if(laskuri(ElaskurinToimintamoodi_lisaa)==0)
{
printf("virhe laskurissa");
return -1;
};
if(laskuri(ElaskurinToimintamoodi_vahenna)==0)
{
printf("virhe laskurissa");
return -1;
};
}
return 0;
}

int laskuri(ElaskurinToimintamoodi toiminto)
{
static int luku=0;
switch(toiminto)
{
case ElaskurinToimintamoodi_lisaa:
luku++;
break;
caseElaskurinToimintamoodi_vahenna:
luku--;
break;
case ElaskurinToimintamoodi_palauta:
return luku;
break;
default:
printf("Virhe! Tuntematon toimintamoodi annettu");
return 0;
break;
}
}


Kuutioidaanpa hetki tuota laskuri funktiota. Eli funktiolle annetaan parametrina enumin arvo, joka kertoo mitä laskurin tahdotaan tekevän. Itsestään selvää. Mutta mitä mitä. Nythän laskurin arvo pidetään funktion sisäisessä muuttujassa "luku", eikä globaalissa. Normaalistihan tämä muuttuja menettäisi arvonsa aina kun funktiosta poistutaan. Tässä nyt kuitenkin arvo säilyy, avainsana static:in ansiosta. Pohditaanpa vielä hetki. lukuhan määritellään funktion alussa: static int luku=0; Miksei luku muuttujaan siis joka kutsulla talleteta nollaa, ja sekoiteta näin laskuriamme? Static sanan toinen merkillisyys on siinä, että static muuttuja alustetaan vain kerran, ohjelman suorituksen alkaessa. Koska muuttuja ei ikinä poistu muistista (ikinä on nyt siihen asti kuin ohjelman suoritus kestää, eli esimerkin tapauksessa, nykyajan koneilla muutamia millisekunteja. Suhteellisuusteoria on siis todistettu ajan suhteellisuuden osalta.)

Ja viimein. C++:ssa static:lla on siis lisäksi omat tarkoituksensa, ja siitä ehkä jokin toinen kerta...

1 kommentti:

Maz kirjoitti...

C ja C++ static:n eroja selvennetty, kiitos Metabolixin kommentin.