Ange ett tal: 5 Ange ett tal: 6 Genomsnitt: 5.500000 |
Programmet, som visas nedan, är tyvärr inte helt korrekt, och när vi försöker bygga och köra det får vi de angivna fel- och varningsmeddelandena. I vilka av kompilatorns faser, eller vid andra tidpunkter, upptäcks vart och ett av de olika felen och varningarna?
(Man kan behöva åtgärda en del av felen för att komma vidare och få de andra felen.)
Programmet | Meddelanden |
---|---|
#include <stdio.h> ibt main(void) { int tal1 tal2; printf("Ange ett tal: "); scanf("%d", &tal1); printf("Ange ett tal: "); scanf("%d", &tal1) double snitt = 1.0 * (tal1 + tal2) / 2; print("Genomsnitt: %f\n", snitt); } |
(1) unknown type name "ibt" (2) expected "=", "," or ";" before "tal2" (3) expected ";" before "double" (4) "tal2" is used uninitialized (5) undefined reference to "print" |
Svar:
(1) semantisk analys (men, pga hur C fungerar, kan även parser vara ett rimligt svar)
(2) parser (3) parser (4) maskinoberoende optimering, eller möjligen semantisk analys (5) länkaren |
Svar:
Det har att göra med den semantiska analysen,
och vilka datatyper som den ger till de olika delarna av programmet.
I den semantiska analysen kommer uttrycket (tal1 + tal2) / 2 att analyseras,
och vi ser då att tal1 och tal2 är variabler som har datatypen int.
Därför bli additionen en heltalsaddition, som också ger ett int-värde som resultat.
Därefter delar vi det resultatet med heltalsliteralen 2,
som också har datatypen int.
Reglerna för språket C
(och många andra språk, till exempel Python 2, men inte Python 3)
säger att divisionen
då är en heltalsdivision, som ger ett int-värde som resultat.
Exempelvis blir (5 + 6) / 2 heltalet 5 och inte flyttalet 5.5, som vi vill.
Om vi sen lägger det värdet i double-variabeln snitt
kommer det att konverteras från int till double,
men då är decimalerna redan borta, så variabeln får double-värdet 5.0.
När vi multiplicerar (tal1 + tal2) med flyttalsliteralen 1.0 upptäcker den semantiska analysen att vi multiplicerar ett heltal med ett flyttal. Då kommer först heltalssumman (tal1 + tal2) att konverteras till ett flyttal, och därefter görs flyttalsmultiplikation. Resultatet av 1.0 * (tal1 + tal2), som är ett flyttal, ska divideras med heltalsliteralen 2, som är ett heltal, och då säger reglerna att 2 ska konverteras till ett flyttal, och därefter görs flyttalsdivision. Decimalerna blir kvar. |
S -> a b T | c d U
T -> T e | f
U -> g
a) Grammatiken är inte lämplig för att implementeras med en prediktiv recursive-descent-parser med en token lookahead (en LL(1)-parser). Förklara varför! Vad skulle hända om vi försökte?
Svar:
Grammatiken innehåller vänsterrekursion i produktionen T -> T e.
Recursive-descent-parsern skulle hamna i en oändlig rekursion
när den försöker parsa icke-terminalen T och hittar terminalen f.
Anropet till T() inuti T() sker innan någon token förbrukats:
void T(void) { if (lookahead == 'f') { T(); match('e'); } else { match('f'); } } |
b) Visa hur inmatningen a b f e e e parsas av en bottom-up-parser med en stack. Visa vilka skift- och reduce-operationer som utförs, och hur stacken ser ut efter varje operation.
Svar:
1. a skiftas till stacken. Stacken:
a 2. b skiftas till stacken. Stacken:
b
3. f skiftas till stacken. Stacken:
f
4. f reduceras till T enligt produktionen T -> f. Stacken:
T
5. e skiftas till stacken. Stacken:
e
6. T e reduceras till T enligt produktionen T -> T e. Stacken:
T
7. e skiftas till stacken. Stacken:
e
8. T e reduceras till T enligt produktionen T -> T e. Stacken:
T
9. e skiftas till stacken. Stacken:
e
10. T e reduceras till T enligt produktionen T -> T e. Stacken:
T
11. a b T reduceras till S enligt produktionen S -> a b T. Stacken: S |
(1) t1 = y * 2 (2) t2 = z * 3 (3) t3 = t1 + t2 (4) if x >= t3 goto (11) (5) t4 = u * 4 (6) u = t4 + 3 (7) t5 = u * 4 (8) t = t5 + 5 (9) t6 = u * 4 (10) v = t6 + 6 (11) t7 = u * 4 (12) w = t7 + 7 |
a) En del av koden verkar onödigt lång, med alla dessa temporära variabler som t1, t2 och så vidare. Kunde inte de tre första instruktionerna ersättas med en enda instruktion, t3 = y * 2 + z * 3? Om inte, varför?
Svar:
Det är treadresskod, inte femadresskod! Man ska till exempel kunna lagra en treadresinstruktion i en post ("struct"), med ett typfält och därefter tre "adresser", som kan vara variabler eller konstanter. Dessutom kan de vara instruktioner att hoppa till. |
b) Vilka basic blocks finns det?
Svar:
Block 1: instruktion 1-4
Block 2: instruktion 5-10 Block 3: instruktion 11-12 |
c) Visa en optimering som går att göra på koden!
Svar:
Eliminering av gemensamma deluttryck:
I instruktion (9), t6 = u * 4, kan u * 4 ersättas med t5,
som redan innehålller värdet av u * 4.
Därefter kan vi med copy propagation i instruktion (10) ersätta t6
med t5, och helt eliminera t6.
Vi kan inte göra den optimeringen i instruktion (7) och (11). |
Svar:
Datum, tid, ord, kolon, semikolon och nyckelordet klart. |
b) En del av terminalerna har inte fixa lexem, utan kan se olika ut. Skriv, för varje sådan terminal, ett reguljärt uttryck som beskriver hur den får se ut!
Svar:
Datum: [12][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]
Tid: [012][0-9]:[0-5][0-9] Ord: [a-zåäöA-ZÅÄÖ]+ |
Svar:
inmatning -> rapporter klartkommando
rapporter -> rapporter rapport rapporter -> ingenting klartkommando -> klart semikolon rapport -> datum tid kolon ordlista semikolon ordlista -> ordlista ord ordlista -> ingenting |
Kommentarer:
2023-01-10 08:27: krock; 2023-01-10 08:27: episk krock; klart;
Svar:
Svar:
I grammatiken som vi skrivit ovan finns två vänsterrekursioner
som först måste elimineras.
Vi kan skriva om grammatiken så vi får en ny grammatik som beskriver samma språk:
inmatning -> rapporter klartkommando
Ladda ner: skoterparser.c, plus en hjälpfil i form av en Lex-scannerspecifikation, skotrar.l, och en Bison-version av grammatiken, skotrar.y. Det finns också en Makefile för att bygga parsern. Token-typer: DATUM TID KOLON ORD SEMIKOLON KLART
int lookahead; void match(int expected) { if (lookahead != expected) error(); else lookahead = scan(); } void inmatning(void) { rapporter(); klartkommando(); } void rapporter(void) { if (lookahead == DATUM) { rapport(); rapporter(); } else { // empty } } void klartkommando(void) { match(KLART); match(SEMIKOLON); } void rapport(void) { match(DATUM); match(TID); match(KOLON); ordlista(); match(SEMIKOLON); } void ordlista(void) { if (lookahead == ORD) { match(ORD); ordlista(); } else { // empty } } void parse() { lookahead = scan(); inmatning(); printf("Ok.\n"); } int main() { printf("Skriv en inmatning av rapporter om elskotrar!\n"); printf("Avsluta med CTRL-D!\n"); parse(); } |