Dagens tema Kort repetisjon Unioner Cs preprosessor Allokering av variable Separat kompilering Programmet make Datastrukturer Matriser Dynamiske matriser Ringbuffere Oblig2 Obligatorisk oppgave nr. 2 slippes på fredag! Ark 1 av 27 Forelesning 14.3.2001
Repetisjon av flyt-tall IEEE 754 har satt standard for lagring av 32- og 64-bits flyttall: Lagring av 32-bits flyttall: 31 30 23 22 0 S Eksponent Mantisse Lagring av 64-bits flyttall: 63 62 52 51 0 S Eksponent Mantisse Som spesialkonvensjon er tallet 0 representert av kun 0-bit I MIPS håndteres flyt-tall av en egen koprosessor med nummer 1. Den har sine egne registre $f0, $f1,..., $f31. Operasjoner på flyt-tall i MIPS utføres ved egne instruksjoner. Forelesning 14.3.2001 Ark 2 av 27
Flyttall i C Float/double float f; /* 32-bits flyttall */ double pi=3.14159265; /* 64-bits flyttall */ Formatert utskrift: printf("float: %.4f og double: %2.10lf\n", f, pi); Formatert innlesing: scanf("%f %lf", &f, &d); Forelesning 14.3.2001 Ark 3 av 27
Eksempel IN 147 Program og maskinvare div100 dividerer et 64-bits flyt-tall med 100: #include <regdef.h>.text.globl div100 # Navn: div100. # Synopsis: Dividere en double med 100. # Signatur i C: double div100( x); div100: li t0,100 # Hent verdien 100 mtc1 t0,$f0 # inn i $f0 og cvt.d.w $f0,$f0 # omdann til double. div.d $f0,$f12,$f0 # dividerer x på 100. jr ra # Retur. C-programmet ore-til-kr.c #include <stdio.h> /* bruker div100 for å regne om et ebløp i øre til kroner */ extern double div100(double x); int main(void) { double ore = 24530, kr = div100(ore); printf("%.2f øre er %.2f kr.\n", ore, kr); return 0; } Kompileringskommandoen maskin navn> cc div100.s ore-til-kr.c -o ore-til-kr div100.s: ore-til-kr.c: maskin navn> ore-til-kr 24530.00 øre er 245.30 kr. Forelesning 14.3.2001 Ark 4 av 27
Unioner Er det mulig å lagre flyt-tall og heltall i samme variabel? Svaret er Ja hvis vi bruker en union. #include <stdio.h> typedef enum { intval, floatval } valkind; typedef struct { valkind kind; union { int ival; float fval; }val; } value; void printval(value v) { if (v.kind == intval) printf("%d", v.val.ival); else printf("%f", v.val.fval); } int main(void) { value one, pi; one.kind = intval; one.val.ival = 1; pi.kind = floatval; pi.val.fval = 3.14159265; printf("one: "); printval(one); printf(", pi: "); printval(pi); printf("\n"); printf("en value er på %d byte.\n", sizeof(value)); return 0; } Forelesning 14.3.2001 Ark 5 av 27
Her lagres ival og fval «oppå» hverandre i val: kind fval/ival Vi kan velge å se på bit-mønstreret i val som et heltall (med ival) eller et flyt-tall (med fval). Resultatet av kjøringen er som følger: one: 1, pi: 3.141593 En value er på 8 byte. Forelesning 14.3.2001 Ark 6 av 27
Cs preprosessor Før selve kompileringen går C-kompilatoren gjennom koden med en preprosessor (som er programmet /usr/lib/cpp). Dette er en programmerbar tekstbehandler som gjør følgende: Henter inn filer #include "incl.h" #include <stdio.h> Hvis filen er angitt med spisse klammer (som for eksempel <stdio.h>), hentes filen fra området /usr/include. Ellers benyttes vanlig notasjon for filer. Forelesning 14.3.2001 Ark 7 av 27
Leser makro-definisjoner og ekspanderer disse i teksten: #define ATARI_ST #define N 100 #define MIN(x,y) ((x)<(y)? (x) : (y)) Av gammel tradisjon gis makroer navn med store bokstaver. Benytter man makroer med parametre, bør disse settes i parenteser. Likeledes, hvis definisjonen er et uttrykk med flere symboler, bør det stå parenteser rundt hele uttrykket. Betinget kompilering. Her angis hvilke linjer som skal tas med i kompileringen og hvilke som skal utelates. Forelesning 14.3.2001 Ark 8 av 27
Betinget kompilering Følgende direktiver finnes for betinget kompilering: #if Hvis uttrykket etterpå er noe annet enn 0, tas etterfølgende linjer tas med. Uttrykket kan ikke inneholde variable eller funksjoner. #ifdef Hvis symbolet er definert (med en #define), skal etterfølgende linjer tas med. #ifndef Motsatt av #ifdef. #else Skille mellom det som skal tas med og det som ikke skal tas med. #endif Slutt med betinget kompilering. Eksempel: #define ATARI_ST #ifdef ATARI_ST int x; #else long x; #endif Forelesning 14.3.2001 Ark 9 av 27
Det er også mulig å styre betinget kompilering gjennom cc-kommandoen: maskin navn> cc -c -DATARI_ST gir samme effekt som om det sto #define ATARI_ST i program-koden. På denne måten er det mulig å ha flere versjoner av koden (for eksempel for flere maskin-typer) og så kontrollere dette utelukkende gjennom kompileringen. Fare med betinget kompilering Man kan risikere å ha kode som aldri har vært kompilert, og som kan inneholde de merkeligste feil. Forelesning 14.3.2001 Ark 10 av 27
Allokering av variable I C kan man angi hvordan (og til dels hvor) variable skal allokeres. Mulighetene er: Automatic er lokale variable i funksjoner. Det settes av plass til variablene på stakken når funksjonen kalles, og plassen frigjøres når funksjonen er ferdig. En initiering foretas hver gang variabelen oppstår. static er variable som det settes av plass til når programmet lastes opp for kjøring. De ligger alltid på samme sted, og hvis de er lokale til en funksjon, vil de bevare verdien fra ett kall til det neste. En eventuell initiering foretas altså kun én gang. NB! static-spesifikasjonen har en annen sideeffekt når den benyttes på globale deklarasjoner (både variable og funksjoner): Navnet på deklarasjonen blir ukjent utenfor filen. Forelesning 14.3.2001 Ark 11 av 27
extern indikerer at variabelen eller funksjonen er deklarert et annet sted. Bindingen til denne andre variabelen skjer under linkingen. register indikerer at variabelen bør ligge i et register. På denne måten kan man hjelpe kompilatoren til å lage mer effektiv kode. Vi kan lage følgende tabell: automatic static extern register Globale variable S/OK OK Globale funksjoner OK OK Parametre S OK Lokale variable S OK OK OK Lokale funksjoner OK Her angir S at spesifikasjonen er standard, men OK angir at det er et valgbart alternativ. Forelesning 14.3.2001 Ark 12 av 27
Separat kompilering I utgangspunktet er det ingen problem med separat-kompilering i C; hver fil utgjør en enhet som kan kompileres for seg selv, uavhengig av alle andre filer i programmet. maskin navn> cc -c del.c vil kompilere filen del.c og lage del.o som inneholder den kompilerte koden. Imidlertid er det en fare for at strukturer, makroer, typer og andre elementer ikke blir skrevet likt i hver fil. Dette løses ved hjelp av definisjonsfiler («header files»), hvis navn gjerne slutter med.h. Filen incl.h: #define N 100 typedef char *string; Forelesning 14.3.2001 Ark 13 av 27
Filen prog.c: #include "incl.h" int main(void) { string s[n]; }. Definisjonsfiler inneholder gjerne følgende: Makrodefinisjoner (#define) Typedefinisjoner (typedef, union, struct) Eksterne spesifikasjoner (extern) Funksjonssignaturer som extern int f(int,char); (Legg merke til semikolonet sist!) Forelesning 14.3.2001 Ark 14 av 27
Programmet make Det er mange praktiske problemer forbundet med programmering av et større program: Hvordan holde orden på et stort program når det er mange separatkompilerte filer? Hvis én fil endres, hvilke filer må da kompileres på nytt? Hvordan sikre at alle de riktige opsjonene er med ved hver kompilering? Hvordan unngå at brukeren skriver seg i hjel på lange kommandolinjer? Løsningen er make. Forelesning 14.3.2001 Ark 15 av 27
Brukeren lager en fil med navn makefile eller Makefile. Denserslikut: F 0 :F 1 F 2... F n Tab Kommandolinje Tab Kommandolinje. Dette betyr: 1. Filen F 0 er «avhengig» av filene F 1, F 2,..., F n. Det betyr at hvis F 0 er eldre enn noen av disse, må F 0 rekompileres. 2. Kommandolinjene må da utføres for å rekompilere F 0. Disse linjene må starte med en Tab. Forelesning 14.3.2001 Ark 16 av 27
Programmet make gjør altså følgende: 1. Først leses filen makefile eller Makefile. 2. Så bygger make opp en oversikt (nærmere bestemt en graf) over hvilke filer som avhenger av hvilke. Utgangspunktet (roten) for denne grafen oppgis som parameter til make; ellers tas første fil. Denne grafen angir hvilken rekkefølge filene skal sjekkes (og eventuelt rekompileres) i. For C-programmer spiller ikke dette noen rolle, men for noen språk (f.eks. Simula) er dette vesentlig. 3. Nå kan hver fil sjekkes i riktig rekkefølge. Hvis (a) filen ikke finnes, eller (b) noen av filene den er avhengig av ikke finnes, eller (c) filen er eldre enn noen av de den er avhengig av, blir den rekompilert. Brukeren får beskjed om hvilke kommandoer som utføres. Forelesning 14.3.2001 Ark 17 av 27
Et eksempel: incl.h: #define N 100 func.c: #include "incl.h" int N2(void) { return 2*N; } prog.c: #include <stdio.h> #include "incl.h" extern int N2(void); int main(void) { if (N2() == 2*N) printf("n2 er OK.\n"); else printf("n2 er feil!\n"); return 0; } Dette fordrer følgende makefile: prog: prog.o func.o cc prog.o func.o -o prog prog.o: prog.c incl.h cc -c prog.c func.o: func.c incl.h cc -c func.c Forelesning 14.3.2001 Ark 18 av 27
Nå kan dette prøves: maskin navn> make cc -c prog.c cc -c func.c cc prog.o func.o -o prog maskin navn> prog N2 er OK. maskin navn> touch prog.c maskin navn> make cc -c prog.c cc prog.o func.o -o prog maskin navn> touch incl.h maskin navn> make cc -c prog.c cc -c func.c cc prog.o func.o -o prog maskin navn> prog N2 er OK. maskin navn> make make: prog is up to date maskin navn> Forelesning 14.3.2001 Ark 19 av 27
Det er mulig å definere forkortelser (makroer uten parametre): CC = cc CFLAGS = -O -DATARI_ST -DIFI BIN = func.o prog.o prog: $(BIN) $(CC) $(CFLAGS) $(BIN) -o prog prog.o: prog.c incl.h $(CC) $(CFLAGS) -c prog.c func.o: func.c incl.h $(CC) $(CFLAGS) -c func.c Forelesning 14.3.2001 Ark 20 av 27
Matriser Vektorer (én-dimensjonale matriser) C JAVA int a[12]; int a[]; a=newint[12]; Simula integer array A(10:21); Lagring short int arr[6] = {1, 1, 2, 3, 5, 8}; IC: arr + I S. A10A 8 5 A108 5 4 A106 3 3 A104 2 2 A102 1 1 A100 1 0. Generelt: arr + (I L) S Forelesning 14.3.2001 Ark 21 av 27
Indeksering # Kode: Henter et element i vektoren Vektor. # Registre: $8: Indeksen (i området 1 til 10). # $9: Elementet. # Spesielt: Vektor er deklarert med noe à la # Vektor: array [1:10] of Short; (Pascal). addi $8,$8,-1 # Kompensér for nedre grense. add $8,$8,$8 # Doble, pga 2 byte/element. la $9,Vektor # Hent adressen til Vektor. add $8,$8,$9 # Legg til 2 indeks. lh $9,0($8) # Hent elementet. Aksess med peker # Kode: Henter et element pekt på av p. # Registre: $8: Pekeren p. # $9: Elementet. lh $9,0($8) # Hent elementet. Forelesning 14.3.2001 Ark 22 av 27
To-dimensjonale matriser I Simula: integer array Arr2(1:3,1:2); hvor hver integer opptar 4 byte. Disse lagres radvis i de fleste programmeringsspråk. Generelt:. A114 (3,2) A110 (3,1) A10C (2,2) A108 (2,1) A104 (1,2) A100 (1,1). Arr2 + ((I 1 L 1 ) D 2 + (I 2 L 2 )) S der D i er U i L i + 1. IC:long int arr2[3][2]; arr2 + (I 1 D 2 + I 2 ) S Forelesning 14.3.2001 Ark 23 av 27
Generelt IC: T a[n1][n2] [Nn]; Elementet a[x1][x2]...[xn] befinner seg i lokasjonen som kan forenkles til a + sizeof(t ) n i=1 x i n Nk k=i+1 a + sizeof(t ) (( (x1n2 + x2)n3 + )Nn + xn) Forelesning 14.3.2001 Ark 24 av 27
Dynamiske vektorer Mange språk har muligheter for dynamiske matriser som kan bety to ting: Vektorens lengde er ikke kjent under kompileringen, men er kjent når vektoren skal deklareres. Vektorens lengde kan endres under kjøringen. I C kan man enkelt etterligne begge deler takket være pekere, {m c re}alloc og definisjonen a[i] *(a+i) #include <stdlib.h> : int *arr = calloc(n, sizeof(int)); : arr[0] = arr[1]+4; : arr = realloc(arr, (n+10)*sizeof(int)); arr[n+9] = -1; : free(arr); Forelesning 14.3.2001 Ark 25 av 27
Ringbuffere Ringbuffere brukes til å implementere køer («FIFO-lister»). Ingen høynivåspråk har noen tilsvarende struktur. Ut Inn Pekerne forteller hvor neste element skal inn, og hvor man kan finne neste element som skal ut. Implementasjon En ringbuffer implementeres å «klippe den opp» og lagre den i en vektor: Ut xx xx xx xx xx xx Inn Forelesning 14.3.2001 Ark 26 av 27
Deklarasjon i C #define BUFFSIZ 12 int ringb[buffsiz], innp = 0, utp = 0, ant_e = 0; Innsetting i ringbuffer int put(int x) { if (ant_e == BUFFSIZ) return 0; ant_e++; ringb[innp] = x; innp++; if (innp >= BUFFSIZ) innp = 0; return 1; } Henting fra ringbuffer int get(void) { int x; if (ant_e == 0) return -1; ant_e--; x = ringb[utp]; utp++; if (utp >= BUFSIZE) utp = 0; return x; } Forelesning 14.3.2001 Ark 27 av 27