Programmeringsspråket C Del 3 Michael Welzl 1
Vektorer og minneallokering Vektorer blir ikke initialisert å allokere en vektor er bare en statisk måte å allokere minne på. char a, b[4], c; a = 3; b[0] = 7; b[a] = 8; 2
Vektorer og pekere Vektornavn kan brukes som en peker til element nr. 0: int a[88];...... a &a[0] Når en vektor overføres som parameter, kan det altså brukes som en peker til starten. 3
Pekere og indekser Aksess av vektorelementer kan også uttrykkes med pekere: a[i] *(a+i) Det er altså det samme om vi skriver a[3] eller *(a+3). 4
Følgende to funksjoner er derfor fullstendig ekvivalente int strlena (char str[]) { int ix = 0; while (str[ix]) ix++; return ix; } int strlenb (char *str) { char *p = str; while (*p) p++; return p-str; } 5
Størrelser og adresser De fleste datamaskiner idag er byte-maskiner der man adresserer hver enkelt byte; char lagres i én byte. short lagres i minst to bytes. long lagres i minst fire bytes. Prosessorene har instruksjoner som kan hente et gitt antall bytes av gangen. Dette kalles bussbredden og er oftest 2, 4 eller 8 idag. En 64-bits prosessor kan hente 8 bytes av gangen. 6
Regning med pekere Så lenge jobbet vi med en char-vektor, men hva om den er en long som trenger 4 byte til hvert element?..og hvorfor kan vektorer brukes som vanlig i C i det hele tatt for noe annet enn char? Egne regneregler for pekere: C har egne regneregler for pekere (og dermed også bruk av vektor-indekser): p+i betyr Øk p med i multiplisert med størrelsen av det p peker på. 7
Regning med pekere: eksempel #include <stdio.h> // noe nytt: fra nå av kan vi bruke forkortelsen "ul typedef unsigned long ul; int main(void) { char *cp = (char*)0x123400; long *lp = (long*)0x123400; cp++; lp++; printf("cp = Ox%lx\nlp = 0x%lx\n", (ul)cp, (ul)lp); } Gir følgende resultat når det kjøres: cp = Ox123401 lp = 0x123408 8
Dynamisk allokering Ofte trenger man å allokere minne under kjøringen i tillegg til variablene. Standardfunksjonen malloc ( memory allocate ) benyttes til dette. Parameter er antall byte den skal allokeres; operatoren sizeof kan gi oss dette. Vi må ha med stdlib.h for at malloc kan brukes. #include <stdlib.h>.. int *p;.. p = malloc(sizeof(int)); Frigivelse av minne: Når minneblokken ikke trengs mer, må den gis tilbake til systemet med funksjonen free: free(p); 9
malloc eksempel Anta at vi skal lese et navn (dvs. en tekst) og skrive det ut. For at navnet ikke skal oppta plass når vi ikke trenger det, bruker vi dynamisk allokering. #include <stdio.h> #include <stdlib.h> int main(void) { char *navn; printf("hva heter du? "); navn = malloc(200); scanf("%s", navn); printf("hei på deg, %s.\n", navn); free(navn); } 10
Pekere til pekere til... Noen ganger trenger man en peker til en pekervariabel, for eksempel fordi den skal overføres som parameter og endres. Siden vanlige pekere deklareres som: XXX *p; Må en peker til en peker angis som: XXX **p; Dette kan utvides med så mange stjerner man ønsker. 11
Pekere til pekere: eksempel Omgivelsesvariable i Unix inneholder opplysninger om en bruker og hans eller hennes preferanser: LOGNAME=dag PAGER=less HOSTTYPE=sgi PRINTER=prent HOME=/home/ansatte/03/dag SHELL=/local/gnu/bin/bash Omgivelsen overføres nesten alltid fra program til program ved en global variabel: extern char **environ; 12
Eksempel (forts.) Pekeren environ peker på en vektor av pekere som hver peker på en omgivelsesvariabel og dens definisjon: 13
Vanlige pekerfeil Det er noen feil som går igjen: Glemme initiering av pekeren long *p; printf("verdien er: %ld.\n", *p); Glemme frigjøring av minneblokk long *p; p = malloc(sizeof(long)); p = NULL; Det allokerte objektet vil nå være utilgjengelig, men vil flyte rundt og oppta plass så lenge programmet kjører. Dette kalles en hukommelseslekkasje. 14
Vanlige pekerfeil (forts.) La en global peker peke på en lokal variabel long *p; void f(void) { long x; p = &x; } f(); p peker no på en variabel som ikke finnes mer. Stedet på stakken der x lå kan være tatt i bruk av andre funksjoner. 15
Enda en vanlig pekerfeil Peke på resirkulert minneblokk long *p, *q; p = q = malloc(sizeof(long)); free(p); p = NULL; q peker nå på en minneblokk som er frigjort og som kanskje er tatt i bruk gjennom nye kall på malloc. 16
Struct-er i C I Java kan vi sette sammen flere datatyper til en klasse. I C har vi noe tilsvarende: 17
Deklarasjon og bruk av struct-variable Bruk av struct-variable: Struct-variable deklareres som andre variable struct a astr; Følgende skiller slike deklarasjoner fra de tilsvarende i Java: Struct-ens navn består av tå ord: struct (som alltid skal være der) og a (som programmereren har funnet på) Man trenger ikke opprette noe objekt med new. Struct-variable brukes ellers som i Java: astr.b = astr.c + 2; if(astr.f<0.0) astr.ch = 'x'; 18
Pekere til struct-er Vi kan selvfølgelig peke på struct-variable: struct a *pa = malloc(sizeof(struct a)); (*pa).f = 3.14; Legg merke til at vi trenger parantesene rundt pekervariabelen fordi *pa.f tolkes som *(pa.f). Fordi vi så ofte trenger pekere til struct-variabler er det innført en egen notasjon for dette: pa->f = 3.14; 19
Eksempel: en enkel lenket liste Fordelene med lister: Dynamiske: plassforbruk tilpasses under kjøringen. Fleksible: innbyrdes rekkefølge kan lett endres. Generelle: kan simulere andre strukturer. Ulemper med lister: Det kan lett bli en del leting, slik at lange lister kan være langsomme i bruk. Om en enkel liste er et bra valg er avhengig av måten den brukes på; Andre dynamiske datastrukturere (stack, array, hashmap, tree,..) har andre fordeler/ulemper. 20
Deklarasjon av listeelementer struct elem { struct elem *neste;... diverse data... }; struct elem *liste; Listepekeren liste peker på første element. 21
Operasjoner på lister Innsetting først i listen p->neste = liste; liste = p; 22
Operasjoner på lister (forts.) Innsetting sist i listen if(liste == NULL) { liste = p; } else { siste = liste; // Finn siste element while(siste->neste = NULL) siste = siste->neste; siste->neste = p; } p->neste = NULL; 23
Innsetting sist i listen (forts.) Dette kan gjøres raskere hvis vi alltid har en peker til siste element. 24
Fjerning av element Vi antar at p skal fjernes fra listen, og at px peker på p sin forgjenger. px->neste = p->neste; free(p); 25
Eksempel med struct, malloc,.. XOR er reversibel A XOR B = C è C XOR B = A Vi kan bruke det for å kryptere en fil på en enkel måte Vi leser hele filen i en buffer, den må allokeres med riktig størrelse Filstørrelsen får vi f.eks. med fstat, den gir oss en struct som inneholder den ifølge manpage. Manpagen sier også at vi må inkludere sys/stat.h Manpagen sier også at fstat forventer et tall istedenfor en peker til FILE, og tallet får vi med fileno I tillegg benytter vi oss av fputc (virker omtrent som fgetc) 26
Kildekode crypt.c #define key 123 // crypto-key #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> int main(int argc, char *argv[]) { FILE *infile, *outfile; struct stat infilestats; char *content; int i; if(argc<3) { printf("usage: crypt infile outfile\n"); return 0; } infile = fopen(argv[1],"r"); if (infile == NULL) { printf("could not read the file %s.\n", argv[1]); return 1; } 27
} if(fstat(fileno(infile), &infilestats)) { printf("could not access file statistics.\n"); return 1; } content = malloc((int)infilestats.st_size); for(i=0; i<infilestats.st_size; i++) content[i] = fgetc(infile) ^ key; // ^ = XOR fclose(infile); outfile = fopen(argv[2],"w"); if (outfile == NULL) { printf("could not open the file %s for writing.\n", argv[2]); return 1; } for(i=0; i<infilestats.st_size; i++) { if(fputc(content[i], outfile) == EOF) { printf("an error occurred during writing.\n"); return 1; } } fclose(outfile); free(content); return 0; Noen free mangler her... 28
} if(fstat(fileno(infile), &infilestats)) { printf("could not access file statistics.\n"); return 1; } content = malloc((int)infilestats.st_size); for(i=0; i<infilestats.st_size; i++) content[i] = fgetc(infile) ^ key; // ^ = XOR fclose(infile); outfile = fopen(argv[2],"w"); if (outfile == NULL) { printf("could not open the file %s for writing.\n", argv[2]); free(content); return 1; } for(i=0; i<infilestats.st_size; i++) { if(fputc(content[i], outfile) == EOF) { printf("an error occurred during writing.\n"); free(content); return 1; } } fclose(outfile); free(content); return 0; 29
C s 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 <stdio.h> #include "incl.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. 30
C s preprosessor (forts.) Leser makro-definisjoner og ekspanderer disse i teksten: #define LINUX #define N 100 #define MIN(x,y) ((x)<(y)?(x):(y)) Av gammel tradisjon gis makroer navn med store bokstaver. (En makro er en navngitt programtekst. Når navnet brukes, blir det ekspandert, dvs erstattet av definisjonen. Dette er ren tekstbehandling uten noen forbindelse med programmeringsspråkets regler.) 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. 31
C s preprosessor (forts.) Betinget kompilering. Her angis hvilke linjer som skal tas med i kompileringen og hvilke som skal utelates. Følgende direktiver finnes for betinget kompilering: #if Hvis uttrykket etterpå er noe annet enn 0, skal 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. 32
Betinget kompilering Eksempel: #ifdef LINUX int x; #else long x; #endif Det er også mulig å style betinget kompilering gjennom gcckommandoen: gcc c -DLINUX gir samme effekt som om det sto det følgende i program-koden: #define LINUX 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. 33
Separat kompilering I utgangspunktet er det ingen problem med separatkompilering i C; hver fil utgjør en enhet som kan kompileres for seg selv, uavhengig av alle andre filer i programmet. gcc c del.c vil kompilere filen del.c og lager del.o som inneholder den kompilerte koden. 34
Separat kompilering: eksempel #include <stdio.h> // Her _deklarerer_ vi en funksjon: definisjonen følger void binaryprintbyte(unsigned char number); int main(void) { int number1, number2; printf("===== Binary logic program =====\n"); printf("enter number 1: "); scanf("%d", &number1); printf("enter number 2: "); scanf("%d", &number2); printf("number 1: "); binaryprintbyte(number1); printf("\nnumber 2: "); binaryprintbyte(number2); printf("\n XOR: "); binaryprintbyte(number1 ^ number2); printf("\n"); } void binaryprintbyte(unsigned char number) { int i; for(i=7; i>=0; i--) printf("%d",((number >> i) & 1)); } 35
To filer binprint.c bin.c #include <stdio.h> void binaryprintbyte(unsigned char number) { int i; for(i=7; i>=0; i--) printf("%d",((number >> i) & 1)); } #include <stdio.h> extern void binaryprintbyte(unsigned char number); int main(void) { int number1, number2; printf("===== Binary logic program =====\n"); printf("enter number 1: "); scanf("%d", &number1); printf("enter number 2: "); scanf("%d", &number2); printf("number 1: "); binaryprintbyte(number1); printf("\nnumber 2: "); binaryprintbyte(number2); printf("\n XOR: "); binaryprintbyte(number1 ^ number2); printf("\n"); } 36
Kompilering Disse kan kompileres hver for seg: gcc c binprint.c gcc c bin.c De kompilerte filene kan siden linkes sammen: gcc binprint.o bin.o -o bin... da får vi et ferdig program som kan kjøres. 37
Definisjonsfiler Det er en fare for at funksjonssignaturer, 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 38
Filen prog.c: Definisjonsfiler (forts.) #include "incl.h" int main(void) { char *s[n];.. } Definisjonsfiler inneholder gjerne følgende: Makrodefinisjoner (#define) Typedefinisjoner (typedef, union, struct) Eksterne spesifikasjoner (extern) Funksjoner som extern int f(int, char); (Legg merke til semikolonet sist Det betyr at funksjonen ikke defineres her, men et annet sted.) 39