tiistai 5. elokuuta 2008

C - kuinka luet tyypimäärittelyjä.







C kielen tyyppimäärittelyt ovat usein hyvin kryptisiä, kun siirrytään perustyypien ulkopuolelle. Ei siis liene kummallista, että törmäsin kerran nettisivuun, jossa C-koodin hyvyyden mittayksiköksi kerrottiin koodikatselmoinnissa kuuluvien kommenttien esiintymistiheyttä, wtf/sec. ("Mitä v*ttua?" sekunnissa). (Nojoo, tämä pätee paljolti muuhunkin kuin tyyppimäärityksiin, mutta ei C:n sekavan näköiset tyypit ainakaan tilannetta helpota).

Tämän takia muistutankin teitä, että vaikka artikkelin luettuanne tiedättekin muistisäännön jonka avulla saa sekavistakin määrityksistä selkoa, niin typedefien käyttö on suotavaa... Kaikkihan eivät blogiani lue :)

Katsotaanpa ensin jokin sekavalta näyttävä määrittely:

char *(*(*foo)[])(char *);


Mikähän olio mahtaa olla kyseessä? Paljastan sen artikkelin lopussa, mikäli et jo siihenmennessä onnistu itse sitä ratkaisemaan.

Yksinkertainen nyrkkisääntö on: Katso ensin oikealle, sitten vasta vasemmalle - ellei sitten sulkeet muuta järjestystä... Eli, aloitetaan aina kummajaisen nimen kohdalta (mikäli muuttujalle on annettu nimi, muuten pitää vain yrittää löytää sisin tietotyyppi...) Sen jälkeen katsotaan mitä on nimen oikealla puolella, sitten mitä vasemmalla, taas mitä oikealla... Paitsi jos sulkeet muuttavat järjestyksen... Kokeillaan:


int foo;


Tapaushan on tietysti selviö, mutta ensin on hyvä koettaa että kikka toimii...

eli aletaan nimestä:
"foo on..."
Katsotaan oikealle - ei mitään. Katsotaan vasemmalle, "..int"
Eli: "foo on int".
YAY! Se toimi!

Uusi yritys:

int *foo[1];


Aletaan nimestä:
"foo on.."
oikealle:
"foo on taulukko"
ja vasemmalle
"foo on taulukko joka sisältää osoittimen (mikäli koko olisi >1, "foo on taulukko joka sisältää osoittimia")
ja oikealla ei mitään, eli vasemmalle:
"foo on taulukko joka sisältää osoittimia intteihin"

YAY YAY! edelleen toimii!

Jatketaan...


void **(*foo)[2]();


eli...
"foo on..."
nyt huomaamme sulkeet, joten katsomme sulkeiden sisälle ennen oikealle puolelle menemistä:
"foo on osoitin..."
sitten oikealle
"foo on osoitin taulukkoon..."
ja vasemmalle:
"foo on osoitin taulukkoon osoittimia..."
ja oikealle..
Nyt on edessä (). Tämä tarkoittaa tietysti funktiota. Eli:
"foo on osoitin taulukkoon osoittimia funktioihin..."
ja vasemmalle:
osoitin, oikealla ei muuta, vasemmalla vielä void
"foo on osoitin taulukkoon osoittimia funktioihin jotka palauttavat osoittimen 'voidiin'.", eli paremmalla suomella
"foo on osoitin taulukkoon osoittimia funktioihin jotka palauttavat void tyyppisen osoittimen."

Todella YAY!

Mutta pakko tunnustaa, että viimeisin tuntui jo aavistuksen epätoivoiselta... Katsotaanpa miten typedefeillä voitaisiin selkeyttää tilannetta:

typedef void *(*voidfunc)() //osoitin funktioon joka palauttaa void *:n
typedef voidfunk *osoitinvoidfunktioon //osoitin funktioon joka palauttaa void *:n

typedef osoitinvoidfunktioon taulukko_voidfunktio_osoittimia[2] //taulukko osoittimia void *:n palauttaviin funktioihin.
taulukko_voidfunktio_osoittimia *foo;

Ja jo pelkän ensimmäisen typedefin käyttö olisi selkeyttänyt tilannetta...

voidfunk *(*foo)[];


Ja nyt ratkaisu ensimmäiseen tyyppiin... Vastaus on:


char *(*(*foo)[])(char *);


"foo on osoitin taulukkoon osoittimia funktioon joka ottaa argumentiksi char osoittimen ja palauttaa osoittimen chariin".

Ja koska kello on taas liian paljon, en jaksa tarkistaa tätä tänään... Vaan painan pääni tyynyyn, nähden painajaisia huomisesta työpäivästä häröpointtereiden ja gummallisten castausten parissa...

2 kommenttia:

Maz kirjoitti...

Kiitokset jälleen Metabolixille bugien huomaamisesta! 10.09.2008 Muutettu:

char **(*foo)[](char *);
=>
char *(*(*foo)[])(char *);


int* *(*foo)(int *,char *)(double,int *);
=>
int* (*(*foo)(int *,char *))(double,int *);

Maz kirjoitti...

jälkimmäinen korjaus kuuluikin seuraavaan tekstiin... (funktio-osoittimiin)