Kapitel 4. Parsning - hvordan oversættes et C program

4.1. En declaration parser

Erklæringer kan være vanskelige at læse, især når der indgår pointere til funktioner. Installation af en signal handler med funktionen signal(2) er kendt for sin vanskelige prototype.

Det ville være en god øvelse at skrive en komplet declaration parser (og en sådan er på ønskesedlen til en udvidet version af denne bog). Imidlertid findes der allerede en meget instruktiv parser til interaktiv / didaktiv anvendelse, cdecl.

Cdecl manual-page går ud fra, at man er bekendt med de væsentligste problemstillinger, men den forklarer ikke, hvordan man løser læselighedsproblemet, hvis man ikke lige har cdecl ved hånden!

For at gøre tingene vanskeligere, er Linux/Gnu signal.h fuld af defines og særlige syntaktiske konstruktioner, som skal lette læsningen for den erfarne multiplatform programmør - men som gør det fuldstændigt umuligt for den almindelige begynder at finde hoved og hale. I dette tilfælde er manual page for signal(2) en lettelse. Der er oven i købet en forklaring på, hvordan man kan opbygge deklarationen ved hjælp af "typedef"ning. Manual siderne for glibc er med i RedHat og andre distributioner, men er ikke en del af glibc systemet, der kun anvender info-pages.

Men i header filerne - kast et blik på /usr/include/signal.h - er der så mange hensyn til diverse platforme at det bliver næsten ulæseligt. Leder vi på "signal(" finder vi:

#define signal(sig, handler) __sysv_signal ((sig), (handler))

Ovenstående define kan man ikke fodre cdecl med. Heldigvis er den ikke så svær at forstå. Signal er en funktion som skal erstattes af __sysv_signal. De to parametre skal gives videre som de er. Det ene skal være et signal, (fx. INTR, svarende til control-C) og det andet skal være den funktion, som vi vil have kørt, når vores program modtager signalet. Men så må vi jo kigge efter, hvordan headerfilen definerer __sysv_signal().

extern __sighandler_t __sysv_signal __P ((int __sig, __sighandler_t __handler));

Heri indgår der - desværre - også en #define macro, nemlig __sighandler_t. Så det er en større sag at finde rundt i.

Det, som cdecl er glimrende til, er at fodre den med en vanskelig prototype og så se, hvordan den vil forklare det.

Vi finder med man signal følgende prototype:

void (*signal(int signum, void (*handler)(int)))(int);

Er det en void funktion? Jeg spørger bare ... Nej, det er ikke en void funktion, det er en funktion, som returnerer en pointer til en anden funktion, som er void. Nemlig den tidligere signal handler. Så kan man jo geninstallere den, hvis man på et tidspunkt skal tilbage til forrige niveau af signal handling.


ax@pluto:/udvik/$cdecl
cdecl>void (*signal(int signum, void (*handler)(int)))(int)
syntax error

Ja, desværre kan denne udmærkede lille applikation heller ikke klare denne iøvrigt korrekte prototype, så der er virkelig et problem her.

Løsningen er at lære sig "højre-venstre" teknikken.

Højre venstre - teknikken består i at læse indefra den identifier, som man ønsker at forstå. I ovenstående erklæring "signal".

Til HØJRE for signal er der en parentes start. Det betyder: "Signal er en funktion ..."

Efter parameter parentesen er der "lukket" ved hjælp af en slut-parentes ekstra. Derfor må vi gå til VENSTRE. Vi er nu nået til, at vi forventer angivelse af retur-type.

Til venstre står der '*' hvilket vi læser som "returnerer en pointer til ..." - til hvad?

Parentes start spærrer for yderligere adgang til venstre, så vi går mod højre, udenfor den matchende parentes og ser efter denne endnu en parentes, aha, en pointer TIL EN FUNKTION, der står jo igen parenteser, og i øvrigt med en int som parameter. Det mest vanskelige er, at den VOID, som står forrest på linjen, er retur type angivelse til denne funktionspointer.

Det er ikke nemt. Læs man - siden for signal, den forklarer (som nævnt ovenfor), at meningen med signal er, at den returnerer den tidligere handler, så man kan reinstallere den senere.