Dagens tema Vanlige feil ved bruk av pekere Feilsøking Debuggere lint Kompilatormeldinger Egne testutskrifter Flyt-tall (P&H: 4.8) Representasjon av flyt-tall Standarden IEEE 754 MIPS-operasjoner på flyt-tall Ark 1 av 36 Forelesning 7.3.2001
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 objekt: long *p; p = (long*)malloc(sizeof(long)); p = NULL; Det allokerte objektet vil nå være utilgjengelig, men vil fortsatt «flyte rundt» og oppta plass så lenge programmet kjører. Dette kalles en minnelekkasje. Forelesning 7.3.2001 Ark 2 av 36
La en global peker peke på lokal variabel: long *p; void f(void) { long x; } p = &x; f(); p peker nå på en variabel som ikke finnes mer. Stedet på stakken der x lå, kan være tatt i bruk av andre funksjoner. Peke på resirkulert objekt: long *p, *q; p = q = (long*)malloc(sizeof(long)); free(p); p = NULL; q peker nå på et objekt som er frigjort og som kanskje er tatt i bruk gjennom nye kall på malloc. Forelesning 7.3.2001 Ark 3 av 36
Feilsøking Hva gjør vi når alt bare går galt? #include <stdio.h> #include <stdlib.h> int *vec[10]; int main(void) { int i; for (i = 0; i<10; ++i) { vec[1] = (int*)malloc(sizeof(int)); *vec[i] = i; } } for (i = 9; i>=0; --i) { printf("vec[%d] peker på %d.\n", i, *vec[i]); } return 0; Forelesning 7.3.2001 Ark 4 av 36
Forsøk på å kjøre dette programmet gir bare Segmentation Fault Dette forteller om en ulovlig peker/adresse, men hvor? I hvilken programkodelinje skjedde feilen? Hva var situasjonen da? Nærmere bestemt vil vi gjerne vite Hva inneholder de ulike variablene? Hva er kallkjeden (dvs. hvilke funksjoner er kalt, og hvorfra)? Forelesning 7.3.2001 Ark 5 av 36
Debuggere En «debugger» er et meget nyttig feilsøkingsverktøy. Det kan analysere en program-dump, vise innholdet av variable, vise hvilke funksjoner som er kalt, kjøre programmet én og én linje, og kjøre til angitt stoppunkter. Debuggeren gdb er laget for å brukes sammen med både cc og gcc. Den har et vindusgrensesnitt som heter ddd. For å kunne bruke gdb/ddd må to ting gjøres: kompilere programmene med opsjonen -g angi at vi ønsker og har ubegrenset plass til programdumper: ulimit -c unlimited hvis vi bruker kommandotolkeren bash. Vi må også huske å fjerne programdumpfilene selv; de er store! Forelesning 7.3.2001 Ark 6 av 36
Programdumper Når et program terminerer på grunn av en feil («aborterer»), prøver det ofte å skrive innholdet av hele prosessen til en fil slik at det kan analyseres siden. 1. Programmet kompileres med debuggingsinformasjon: maskin navn> cc -g test-gdb.c -o test-gdb 2. Programmet kjøres: maskin navn> test-gdb Segmentation Fault (core dumped) maskin navn> ls -l core -rw------- 1 kritisk kritisk 95408 Mar 5 15:20 core Dette kalles ofte en «core-dump» siden datamaskinene for 20 40 år siden hadde hurtiglager bygget opp av ringer med kjerne av feritt. I UNIX heter denne filen derfor core. Forelesning 7.3.2001 Ark 7 av 36
maskin navn> /local/hacks/sgi/bin/ddd test-gdb & Forelesning 7.3.2001 Ark 8 av 36
I File-menyen finner vi «Open Core Dump». Forelesning 7.3.2001 Ark 9 av 36
Nå vet vi at feilen oppsto på linje 12 i forbindelse med *vec[i] = i. Kanskje det er noe galt med indeksen i? I Data-menyen finner vi «Display Local Variables» Forelesning 7.3.2001 Ark 10 av 36
Variabelen i er 0, så den er OK. Hva da med vec? Vikan klikke på en forekomst av vec og så «New Display». Forelesning 7.3.2001 Ark 11 av 36
Her ser vi at vec[0] er 0 mens vec[1] peker på noe; det burde vært omvendt! (vec[1] vec[9] skal ennå ikke ha fått noen verdi (siden i er 0).) Altså oppsto feilen under initieringen av vec der det står for (i = 0; i<10; ++i) { vec[1] = (int*)malloc(sizeof(int)); *vec[i] = i; } Vi kan da avslutte ddd med «Exit» i File-menyen. Forelesning 7.3.2001 Ark 12 av 36
Et eksempel til Følgende program skal skrive ut sine parametre og alle omgivelsesvariablene: #include <stdio.h> int main(int argc, char *argv[], char **envir) { char **p = envir, *e; int i; for (i = 0; i < argc; ++i) { printf("parameter %d: %s \n", i, argv[i]); } while (! (*p = NULL)) { e = *p; printf("%s\n", e); ++p; } return 0; } Kompilering går fint: maskin navn> cc -g printenv-feil.c -o printenv-feil men kjøringen går dårlig: maskin navn> printenv-feil a b Parameter 0: printenv-feil Parameter 1: a Parameter 2: b (null) (null). Control + \ Quit (core dumped) Forelesning 7.3.2001 Ark 13 av 36
Hva sier gdb/ddd? maskin navn> /local/hacks/sgi/bin/ddd printenv-feil & Forelesning 7.3.2001 Ark 14 av 36
Her ser vi imidlertid at feilen er oppstått i write, men den kaller vi da ikke i vårt program? Vi ber om «Backtrace» i Status-menyen. Forelesning 7.3.2001 Ark 15 av 36
Vi ser at feilen oppsto i linje 14 i main i kallet på printf. Vi klikker på «Up» fire ganger, og velger så «Display Local Variables» i Data-menyen. Vi ser at p ser OK ut, men e burde vel ikke være 0? La oss kjøre programmet på nytt, men legge inn et stoppunkt øverst i while-løkken. Pek og klikk på linjen, og så på «Break at ()». Så kan vi klikke på «Run» igjen. Forelesning 7.3.2001 Ark 16 av 36
Forelesning 7.3.2001 Ark 17 av 36
Etter at programmet er stoppet før det skal utføre while-løkken for første gang, lar vi det utføre de to første linjene ved å klikke på «Step». Forelesning 7.3.2001 Ark 18 av 36
Her ser vi at e er 0, og det må være galt! (Det var ihvertfall ikke det som var hensikten med programmet...) Dette skjedde i den gale testen while (! (*p = NULL)) { som burde vært skrevet while (*p!= NULL) { eller while (*p) { Konklusjon Et par timer brukt på å lære seg gdb og ddd kan man fort få mangedobbelt igjen for senere i kurset! Forelesning 7.3.2001 Ark 19 av 36
Andre feilsøkingverktøy Programmet lint Dette programmet sjekker C-programmer og rapporterer mulig feil og foreslår hvorledes koden kan forbedres. maskin navn> lint printenv-feil.c (12) warning: assignment operator "=" found where "==" was expected function returns value which is always ignored printf Kompilatormeldinger Noen ganger kan kompilatoren gi fornuftige advarsler om potensielle farer («warnings»). Egne meldinger Det aller beste er å regne med at man gjør feil og legge inn egne utskrifter som kan slås av og på ved behov. Forelesning 7.3.2001 Ark 20 av 36
Programmet purify Dette kommersielle programmet legger inn alle mulig tester som blir utført under kjøringen: maskin navn> purify cc test-gdb.c -o test-gdb Purify 4.1 IRIX6, Copyright (C) 1992-1997 Rational Software Corp.... Processing "/usr/lib/libc.so.1"... Processing "test-gdb" maskin navn> test-gdb Når feil oppstår, kommer et meldingsvindu: Forelesning 7.3.2001 Ark 21 av 36
Heltall I 8, 16 eller 32 bit kan vi lagre til dels store tall (med fortegns-bit): Bit Fra......til 8 128 127 16 32 768 32 767 32 2 147 483 648 2 147 483 647 Dette er imidlertid ikke alltid nok: Ett sekund er 9 192 631 770 perioder av strålingen... (Cesium 133). er et tall som er for stort for 32 bit. Dessuten kan vi trenge tall som ikke er heltall: Tallet π er 3,14159265... Forelesning 7.3.2001 Ark 22 av 36
Flyt-tall Tall kan skrives på flere forskjellige måter: 8 388 708 8,388708 10 6 8,39 10 6 De to siste ( M G E) er såkalte flyt-tall og består av: Mantisse («significand») (M). Grunntall («radix») (G). Eksponent (E). Fortegn. Forelesning 7.3.2001 Ark 23 av 36
Representasjon av eksponenten Man kan tenke seg flere ulike måter å lagre eksponenten på: 1. Eget fortegnsbit. 4 blir 1 0 0 0 0 1 0 0 2. 2-erkomplement. 4 blir 1 1 1 1 1 1 0 0 3. Fast tillegg (f.eks. 127 for 8 bits eksponent). 4 blir 0 1 1 1 1 0 1 1 (Dette kalles «excess 127» på engelsk.) Det siste velges oftest i dag. Det forenkler en del operasjoner på flyt-tall, f.eks. sammenlikning og sortering. Forelesning 7.3.2001 Ark 24 av 36
Representasjon av mantissen En desimal brøk: har desimaler. En binær brøk: 3,14159265 11,0010010 har binærer. Brøken tolkes slik: 2 1 Resultatet er 1 2 1 4 1 8 1 16 1 32 1 64 1 128 11,0010 0 1 0 2 1 + 2 0 + 2 3 + 2 6 = 2 + 1 + 1 8 + 1 64 3,1406 Forelesning 7.3.2001 Ark 25 av 36
En normalisert mantisse er en binær brøk med følgende egenskap: 1 M<G For binær representasjon innebærer dette at 1 M<2 Binæren foran binær-kommaet vil altså alltid være 1 (med mindre hele tallet er 0). Grunntallet Grunntallet er nesten alltid 2. Blir ikke lagret. Forelesning 7.3.2001 Ark 26 av 36
Standarden IEEE 754 for 32-bits flyt-tall 31 30 23 22 0 S Eksponent Mantisse S er fortegnet; 0 for positivt, 1 for negativt. Grunntallet er 2. Eksponenten er på 8 bit og lagres med fast tillegg 127. Mantissen er normalisert og på 24 bit, men kun de 23 etter binærkommaet lagres. Forelesning 7.3.2001 Ark 27 av 36
Hvordan lagres 1,0? 1,0 ti =1,0 to som er normalisert. Eksponent er 0+127=127=1111111 to. Fortegnet er 0. 31 30 23 22 0 0 01111111 00000000000000000000000 Hvordan lagres 0? Som spesialkonvensjon er 0 representert av kun 0-bit: 31 30 23 22 0 0 00000000 00000000000000000000000 Hvordan lagres 12,8125? 12,8125 ti = 1100,1101 to = 1,1001101 to 2 3 Eksponent er 3+127=130=10000010 to. Fortegnet er 1. 31 30 23 22 0 1 10000010 10011010000000000000000 Forelesning 7.3.2001 Ark 28 av 36
Største tall 31 30 23 22 0 0 11111110 11111111111111111111111 omtrent 2 254 127 2 3,4 10 38. (Eksponenten 0 er reservert for tallet 0, eksponenten 255 for NAN, «not a number».) Minste positive tall 31 30 23 22 0 0 00000001 00000000000000000000000 omtrent 2 1 127 1 1,2 10 38. Nøyaktighet Mantissen er på 24 bit, og 2 24 1,7 10 7. Dette gir 7 desimale sifre. Forelesning 7.3.2001 Ark 29 av 36
Standarden IEEE 754 for 64-bits flyt-tall Endringer: 63 62 52 51 0 S Eksponent Mantisse Eksponenten er økt fra 8 til 11 bit. Lagres med fast tillegg 1023. Mantissen er økt fra 24 til 53 bit. Øverste bit lagres stadig ikke. Forelesning 7.3.2001 Ark 30 av 36
Største tall Det største tallet som kan lagres, finner vi utfra formelen 2 (211 2) 1023 2 = 2 1023 2 1,8 10 308 Minste positive tall 2 1 1023 1 = 2 1022 1 2,2 10 308 Nøyaktighet Mantissen er på 53 bit, og 2 53 9,0 10 15. Dette gir nesten 16 desimale sifre. Forelesning 7.3.2001 Ark 31 av 36
Operasjoner på flyt-tall i MIPS I MIPS håndteres flyt-tall av en egen koprosessor med nummer 1. Den har sine egne registre $f0, $f1,..., $f31. En double opptar to registre; det første må ha et partallsnummer. Forelesning 7.3.2001 Ark 32 av 36
Flytting mellom CPU og flyt-tallsprosessoren Man må bruke egne instruksjoner for å flytte data mellom CPU-en og flyt-tallsprosessoren c1: mfc1 $x,$fy # c1 $fy CPU $x mtc1 $x,$fy # CPU $x c1 $fy Ved hjelp av to andre instruksjoner kan man lesefraogskrivetilram: lwc1 $fy,nn($x) # mem[nn+$x] c1 $fy swc1 $fy,nn($x) # c1 $fy mem[nn+$x] For double-verdier må man bruke to instruksjoner for å flytte. Forelesning 7.3.2001 Ark 33 av 36
Konvertering Instruksjonen cvt kan konvertere mellom d som er en double, s someren(«single»)float, eller w som er en long (eller int hvis den også er 32 bit, dvs et «word»). Eksempel cvt.d.w $fy,$fx # (long)$fx (double)$fx Aritmetiske operasjoner MIPS har de vanlige aritmetiske operasjonene: abs.x absoluttverdien. add.x addisjon. c.y.x sammenlign. div.x divisjon. mul.x multiplikasjon. neg.x negasjon. sub.x subtraksjon. x er enten d eller s. y er eq (=), le ( ) eller lt (<). Forelesning 7.3.2001 Ark 34 av 36
Ved sammenligning legges resultatet i et eget statusregister. Det kan testes med bc1f dit # Hopp dit hvis usant. bc1t dit # Hopp dit hvis sant. Funksjonskall Ved funksjonskall gjelder følgende: float-parametre legges i $f12 og $f14, double-parametre i $f12 $f13 og $f14 $f15. Ved mer enn to parametre, eller en kombinasjon av float og andre paramtre, følges en spesiell konvensjon. En float returverdi skal ligge i $f0. Forelesning 7.3.2001 Ark 35 av 36
Et eksempel fladd1 øker et 32-bits flyt-tall med 1: #include <regdef.h>.text.globl fladd1 # Navn: fladd1. # Synopsis: Øker en float med 1. # Signatur i C: float fladd1(float x); fladd1: li t0,1 # Hent verdien 1 mtc1 t0,$f0 # inn i $f0 og cvt.s.w $f0,$f0 # omdann til float. add.s $f0,$f0,$f12 # Addér x. jr ra # Retur. Testprogrammet test-fladd1.c #include <stdio.h> extern float fladd1(float x); int main(void) { float res = fladd1(3.14); printf("svaret er %.4f.\n", res); return 0; } Kompileringskommandoen maskin navn> cc test-fladd1.c fladd1.s -o test-fladd1 test-fladd1.c: fladd1.s: maskin navn> test-fladd1 Svaret er 4.1400. Forelesning 7.3.2001 Ark 36 av 36