Dagens tema Mer om C Cs preprosessor Allokering av variable Separat kompilering Programmet make Pekere i C Operasjoner på pekere Pekere og vektorer Referanseparametre Pekere til «alt» og «ingenting» Dynamisk allokering Ark 1 av 24 Forelesning 24.2.1998
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 UNIX-notasjon for filer. Forelesning 24.2.1998 Ark 2 av 24
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 24.2.1998 Ark 3 av 24
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 24.2.1998 Ark 4 av 24
Det er også mulig å styre betinget kompilering gjennom gcc-kommandoen: gcc -ansi -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 24.2.1998 Ark 5 av 24
Allokering av variable I C kan man angi hvordan (og til dels hvor) variable skal allokeres. Mulighetene er: Automatic er lokale variable i funksjoner. Variablene oppstår når funksjonen kalles, og de resirkuleres når funksjonen er ferdig. En initiering foretas hver gang variabelen oppstår. Kun enkle variable kan initieres. static er variable som oppstår 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 24.2.1998 Ark 6 av 24
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. gcc ignorerer slike tips. 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 24.2.1998 Ark 7 av 24
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. gcc -ansi -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 24.2.1998 Ark 8 av 24
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 (extern int f(int,char);) (Legg merke til semikolonet sist!) Forelesning 24.2.1998 Ark 9 av 24
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 24.2.1998 Ark 10 av 24
Brukeren lager en fil med navn makefile eller Makefile. Den ser slik ut: 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 24.2.1998 Ark 11 av 24
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 24.2.1998 Ark 12 av 24
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"); } Dette fordrer følgende makefile: prog: prog.o func.o gcc -ansi prog.o func.o -o prog prog.o: prog.c incl.h gcc -ansi -c prog.c func.o: func.c incl.h gcc -ansi -c func.c Forelesning 24.2.1998 Ark 13 av 24
Nå kan dette prøves: $ make gcc -ansi -c prog.c gcc -ansi -c func.c gcc -ansi prog.o func.o -o prog $ prog N2 er OK. $ touch prog.c $ make gcc -ansi -c prog.c gcc -ansi prog.o func.o -o prog $ touch incl.h $ make gcc -ansi -c prog.c gcc -ansi -c func.c gcc -ansi prog.o func.o -o prog $ prog N2 er OK. $ make make: prog is up to date $ Forelesning 24.2.1998 Ark 14 av 24
Det er mulig å definere forkortelser (makroer uten parametre): CC = gcc CFLAGS = -ansi -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 24.2.1998 Ark 15 av 24
Pekere Deklarasjon Deklarasjon i C: long *p, *q; Operatorer i C Det finnes kun tre pekeroperatorer i C: &x Gir en peker til x *p Gir det p peker på p->s Det samme som (*p).s Forelesning 24.2.1998 Ark 16 av 24
Bruk av pekere i C int a = 1; /* a inneholder 1 */ int b = 2; /* b inneholder 2 */ int *p1 = &a; /* p1 peker nå på a. Effekten er den samme som om det hadde stått: int *p1; p1 = &a; */ int *p2 = p1; /* p2 peker også på a */ *p2 = 4; /* Nå inneholder a verdien 4 */ p2 = &b; /* Nå peker p2 på b */ *p1 = *p2; /* Nå inneholder a (som p1 peker på) verdien 2 (som kom fra b) */ Forelesning 24.2.1998 Ark 17 av 24
Pekere og vektorer I C er pekere og vektorer to sider av samme sak, siden følgende alltid gjelder: a[i] *(a+i) Dessuten vil en referanse til en vektor alltid gi adressen til den første element: Følgende er altså lov: a &a[0] int arr[100], i = 0, *ip; ip = arr; /* alternativt ip = &arr[0]; */ while (i < 100) { printf("%10d", *ip); ip++; i++; } Forelesning 24.2.1998 Ark 18 av 24
Siden navnet på en vektor egentlig er en peker til første element, parameteroverføres alle vektorer som pekere. Funksjoner kan derfor skrives på to ulike måter: int strlen(unsigned char s[]) { int ix = 0; } while (s[ix]!= \0 ) ix++; return ix; int strlen(unsigned char *s) { int num = 0; } while (*s!= \0 ) { num++; s++; } return num; Kallet på funksjonen blir det samme, så brukeren av funksjonen trenger ikke vite hvorledes den er implementert. Versjoner med pekere er imidlertid nesten alltid raskest. Forelesning 24.2.1998 Ark 19 av 24
Referanseparametre i C Simula: procedure ModMany(X, M1, M2, M3, M4); name M1, M2, M3, M4; integer X, M1, M2, M3, M4; begin M1 := Mod(X,2); M2 := Mod(X,3); M3 := Mod(X,5); M4 := Mod(X,7); end; C: void modmany(int x, int *m1, int *m2, int *m3, int *m4) { *m1 = x%2; *m2 = x%3; *m3 = x%5; *m4 = x%7; } Kall: Simula: ModMany(a, v1,v2,v3,v4); C: modmany(a, &v1,&v2,&v3,&v4); Forelesning 24.2.1998 Ark 20 av 24
Implementasjon av pekere Pekere implementeres ganske enkelt ved å lagre adressen til objektet. På MIPS opptar alltid en peker 32 bit (det samme som en int eller en long). Dette kan være anderledes på andre maskiner. Pekere til alt Det er ikke gitt at alle pekere tar like stor plass. Deklarasjonen void * er en peker som er garantert stor nok. Forelesning 24.2.1998 Ark 21 av 24
Peker til ingenting I f.eks. stdio.h: #define NULL 0 Verdien 0 er fast i C. Man kan altså skrive if (P!= NULL)... if (P)... Ved tilordning bør man alltid typekonvertere: int *p; p = (int*)null; f(p, (int*)null); Forelesning 24.2.1998 Ark 22 av 24
Addisjon og subtraksjon av pekere Addisjon og subtraksjon arter seg litt spesielt: xxx *p; p + n betyr «det n-te elementet etter p». Eksempler: char *pc = (char*) 0x1000; short *ps = (short*) 0x1000; long *pl = (long*) 0x1000; pc = pc+1; /* Nå er pc == 0x1001. */ pc += 2; /* pc == 0x1003. */ ps = ps+1; /* Nå er ps == 0x1002. */ ps += 2; /* ps == 0x1006. */ pl++; /* Nå er pl == 0x1004. */ pl += 2; /* pl == 0x100C. */ Det tilsvarende gjelder for subtraksjon. Forelesning 24.2.1998 Ark 23 av 24
Dynamisk allokering «Nye» data-områder allokeres med funksjonen malloc, som finnes i stdlib.h. Plass til 200 heltall (long): #include <stdlib.h> long *p;. p = (long *)malloc(200*sizeof(long)); Frigivelse free(p); Det er ingen automatisk frigivelse, som i f.eks. Simula. Forelesning 24.2.1998 Ark 24 av 24