IS1200 Datorteknik Övning CE_O4 Maskinnära programmering med C Förberedelser till hemlaboration 1 1
4.1 Little och big endian 2
Nios II? Nios II är Little-endian (men eftersom det är en soft-processor så kan den konfigureras om).
När har endian-ness betydelse? Endian-ness har betydelse när två datorer kommunicerar med varandra Vid olika endian-ness måste en av datorerna kasta om byte-ordningen ( vem är frivillig? ). Vi skall skicka 16 miljoner kronor över internet: 00000001 00000000 00000000 00000000 = 16.777.216 kr men om endian-ness missförstås: 00000000 00000000 00000000 00000001 = 1 kr Otur! Network Byte Order = big-endian
4.2 Alignment.align 2 vanligt direktiv för instruktions-minnet, som har 32-bitars instruktioner. 2^2 = 4 bytes =32 bits. Detta är vanligt också för data-minnet, men följande förekommer också.align 0 2^0 = 1.align 1 2^1 = 2.align 3 2^3 = 8 etc Nios II hårdvaran förväntar sig.align 2 för ldw/stw (och.align 1 för ldh/sth, load/store halfword) 6
ldw när effektivadressen inte är jämnt delbar med 4 adress 40 40 41 42 43 adress 44 44 45 46 47 * Hårdvaran skulle behöva hämta två ord och sätta ihop olika delar av orden * Exempel med effektivadress = 43 * Hårdvaran i Nios II kan inte göra detta 7
4.3 Minnesdisposition.data.bss.data (initialiserade data).bss (oinitialiserade data) 8
4.3 Adressrymd Programkoden, skapad i C-funktioner eller i assemblerfilers.text-segment hamnar i kod-arean 9
4.3 Adressrymd Globala variabler, skapade i början av C-program eller i assemblerfilers.data-segment, sparas under compile-time, och hamnar i data-arean om de är initialiserade. Oinitialiserade globala variabler hamnar i.bss-delen där. 10
Körbar fil och datorns minne körbar fil header data symboltabell kopiering vid programstart minne programkod programkod data bss heap
4.3 Adressrymd Lokala variabler, skapade i C-main, C-funktion eller möjligen i assemblerprograms.text-segment som sparas under run-time hamnar på stacken. 12
4.3 Adressrymd Vet man inte i förväg hur mycket utrymme som behövs för data, kan man använda heapen istället för globala variabler. Då lägger man data där som tillkommer under körningen. Man allokerar då alltid utrymme med malloc och lägger tillbaka med free. 13
4.4 Värden, pekare, referenser, heltalsvariabler Skriv programrader i C som visar hur * och & används för att a) deklarera två heltalsvariabler: a och b C: int a; int b; assembler för Nios II:.data # placera i.bss eller.data.align 2 # på adress delbar med 4 a:.word 0 # ett ord = 4 bytes = 32 bitar med värdet 0 b:.word 0 # ett ord = 4 bytes = 32 bitar med värdet 0 14
4.4 Värden, pekare, referenser, heltalsvariabler * och & b) initialisera heltalsvariabeln b till värdet 27 i samband med deklarationen enl (a). C: int a; int b = 27; assembler för Nios II:.data.align 2 a:.word 0 b:.word 27 # ett ord = 4 bytes = 32 bitar # med värdet 27 (dec) 15
4.4 Värden, pekare, referenser, heltalsvariabler * och & c) deklarera två pekarvariabler, som kan peka på a och b: ptr1 och ptr 2. C: int* ptr1; int* ptr2; /* int* ptr1, ptr2; är fel, men int *ptr1,*ptr2; är också OK */ Assembler för Nios II:.data.align 2 ptr1:.word 0 # ett ord = 4 bytes = 32 bitar med värdet 0 ptr2:.word 0 # ett ord = 4 bytes = 32 bitar med värdet 0 16
4.4 Värden, pekare, referenser, heltalsvariabler * och & d) tilldela a värdet 4711 och b värdet 47. C: a = 4711; b = 47; Assembler för Nios II:.text # placera kod i.text-arean.align 2 # på adress delbar med 4 movia r8, a # r8 pekar nu på a movia r9, 4711 # r9 innehåller värdet 4711 stw r9, 0(r8) # värdet 4711 skrivs till a movia r8,b # r8 pekar nu på b movia r9,47 # r9 innehåller nu värdet 47 stw r9, 0(r8) # värdet 47 skrivs till b 17
4.4 Värden, pekare, referenser, heltalsvariabler * och & e) tilldela pekarvariabeln ett värde, så att den pekar på heltalsvariabeln a C: ptr1 = &a; /* utläses som ptr1 tilldelas värdet av adressen till a */ Assembler för Nios II:.text.align 2 movia r8, a # adressen till a skrivs till r8 movia r9, ptr1 # r9 pekar nu på ptr1 stw r8, 0(r9) # adressen till a skrivs nu till ptr1 18
4.4 Värden, pekare, referenser, heltalsvariabler * och & f) tilldela heltalsvariabeln b samma värde som a, med hjälp av ptr1. C: b = *ptr1; /* b tilldelas det värde som ptr1 pekar på */ Assembler för Nios II:.text.align 2 movia r8, ptr1 # nu pekar r8 på ptr1 ldw r10, 0(r8) # läs ptr1; nu innehåller r10 värdet av ptr1, som pekar på a ldw r11, 0(r10) # läs *ptr1; nu innehåller r11 värdet som ptr1 pekar på, dvs a movia r9, b # nu pekar r9 på b stw r11, 0(r9) # skriv b; nu innehåller b det som ptr1 pekade på (dvs a) 19
4.4 Värden, pekare, referenser, heltalsvariabler * och & g) tilldela pekarvariabeln ptr2 ett värde, så att den pekar på samma variabel som ptr1. C: ptr2 = ptr1; /* variabeln ptr2 tilldelas samma värde som variabeln ptr1 */ Assembler för Nios II:.text.align 2 movia r8, ptr1 # nu pekar r8 på ptr1 movia r9, ptr2 # nu pekar r9 på ptr2 ldw r10, 0(r8) # läs ptr1; nu finns värdet från ptr1 i r10 stw r10, 0(r9) # skriv ptr2; nu har ptr2 samma värde som ptr1 20
4.5 Värden, pekare, referenser, char-variabler Skriv programrader i C, som visar hur * och & används i nedanstående exempel. Översätt varje exempel för hand till Nios2-assembler. Skriv alltså först C och sedan Nios-II-assembler för att a) deklarera två globala variabler av typen char: c och d. C: char c char d; Assembler för Nios II:.data #.align 2 behövs ej c:.byte 0 # 1 byte = 8 bitar med värdet 0 d:.byte 0 # 1 byte = 8 bitar med värdet 0 Blir det någon skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler? Svar JA, bara en byte per variabel och tätt packat. 21
4.5 Värden, pekare, referenser, char-variabler * och & b) initialisera variabeln d till värdet 65 i samband med deklarationen enl (a). Vad blir det för skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler?. C: char c; char d = 65; Assembler för Nios II:.data #.align 2 behövs ej c:.byte 0 # 1 byte = 8 bitar med värdet 0 d:.byte 65 # 1 byte = 8 bitar med värdet 65 (dec) 22
4.5 Värden, pekare, referenser, char-variabler * och & c) deklarera två globala pekarvariabler, som kan peka på c och d: ptr3 och ptr4. Blir det någon skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler?. C: char* ptr3; char* ptr4; Assembler för Nios II:.data.align 2 # på adress delbar med 4, # nödvändigt, pekare är 4 bytes ptr3:.word 0 # 1 ord = 4 bytes = 32 bitar med värdet 0 ptr4:.word 0 # 1 ord = 4 bytes = 32 bitar med värdet 0 Ingen kvalitets-skillnad mot heltal i assembler 23
4.5 Värden, pekare, referenser, char-variabler * och & d) tilldela c värdet M och tilldela d värdet 97. Vad blir det för skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler?. C: c = M ; d = 97; Assembler för Nios II:.txt.align 2 movia r8, c # r8 pekar nu på c movi r9, M # nu räcker det med movi (utan a) stb r9, 0(r8) # ska vara stb, ej stw movia r8, d # r8 pekar nu på d movi r9, 97 # misstänkt negativt?! stb r9, 0(r8) # ska vara stb, ej stw 24
4.5 Värden, pekare, referenser, char-variabler * och & e) tilldela pekarvariabeln ptr3 ett värde, så att den pekar på variabeln c. Blir det någon skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler? C: ptr3 = &c; Assembler för Nios II:.text.align 2 movia r8, c # nu pekar r8 på c movia r9, ptr3 stw r8, 0(r9) # nu pekar ptr3 på c # nu pekar r9 på ptr3 25
4.5 Värden, pekare, referenser, char-variabler * och & f) tilldela variabeln d samma värde som c, med hjälp av ptr3. Vad blir det för skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler? C: d = *ptr3; /* d tilldelas det värde som ptr3 pekar på */ Assembler för Nios II:.txt.align 2 movia r8, ptr3 # nu pekar r8 på ptr3 movia r9, d # nu pekar r9 på d ldw r10,0(r8) # läs ptr3; nu innehåller r10 # värdet av ptr3 som pekar på c ldb r11,0(r10) # läs *ptr3; nu innehåller r11 # värdet som ptr3 pekar på, dvs c stb r11,0(r9) # skriv d; nu innehåller d det som # ptr3 pekade på, dvs c 26
4.5 Värden, pekare, referenser, char-variabler * och & g) tilldela pekarvariabeln ptr4 ett värde, så att den pekar på samma variabel som ptr3. Blir det någon skillnad skillnad i Nios-II-assemblerkoden jämfört med motsvarande operation med heltalsvariabler? C: ptr4 = ptr3; /* */ Assembler för Nios II:.txt.align 2 movia r8, ptr3 # nu pekar r8 på ptr3 movia r9, ptr4 # nu pekar r9 på ptr4 ldw r10, 0(r8) # läs ptr3; nu finns värdet från ptr3 i r10 stw r10, 0(r9) # skriv ptr4; nu har ptr4 samma värde som ptr3 27
(4.5) Enkel C-kod med pekare a) C-koden nedan ska kompileras för hand till assembler för Nios II b) Hur översätts raden a = *ptr? C: int a; int b = 27; int * ptr; int main() { a = 17; ptr = &b; a = *ptr; return( a ); } c) Vilket värde returnerar programmet? assembler:.data.align 2 a:.word 0 b:.word 27 ptr:.word 0 main: # a = 17; # ptr = &b;.text.global main.align 2 movia r8, a movi r9, 17 stw movia movia stw r9, 0(r8) r8, ptr r9, b r9, 0(r8) # a = *ptr # lägg adressen till ptr i r8 movia r8, ptr # läs ptr:s värde ldw r9, 0(r8) # ptr innehåller en adress, # läs i minnet på den adressen ldw r10, 0(r9) # skriv till a movia r11, a stw r10, 0(r11) # return( a ); movia r8, a ldw r2, 0(r8) ret 28
(4.5) Annan C-kod med pekare Kompilera följande korta C-program till assemblerkod för Nios II int c = 47; int * ptr; int foo() { int tmp; ptr = &tmp; *ptr = c; return( tmp ); } 29
(4.5) Annan C-kod med pekare.data.align 2 c:.word 47 ptr:.word 0.text.global foo.align 2 foo: # int tmp; # gör plats för tmp på stack addi sp,sp,-4 # ptr = &tmp; movia r8,ptr stw sp, 0(r8) # nu pekar ptr på tmp # *ptr = c; # lägg adress till c i r9 movia r9,c # läs c ldw r10,0(r9) # lägg adress till ptr i r11 movia r11,ptr # läs ptr ldw r12,0(r11) # följ pekaren och skriv till minnet stw r10,0(r12) # return( tmp ); ldw r2,0(sp) # läs tmp addi sp,sp,4 # remove tmp ret
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcpy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcpy( &b [0] [0], &a [0] [0], sizeof( a ) ); a) Förklara vad en pointer to void är för något. Det är en pekare, som pekar på en plats i minnet, men det är odefinierat vilken typ som pekas ut. Det innebär, att kompilatorn inte vet hur många bytes, som eventuellt ska läsas eller skrivas på den platsen i minnet. Därför krävs av programmeraren att det görs type-cast av en void-pekare, innan den kan användas för en minnesreferens. 31
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcopy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcopy( &b [0] [0], &a [0] [0], sizeof( a ) ); a) Förklara de tre parametrarna till memcpy. Den första, dest, är en voidpekare, som anger första byte i minnet, till vilken kopieringen ska ske. Den andra, src, är en voidpekare, som anger första byte, från vilken kopiering ska ske. Den tredje parametern anger antal bytes, som ska kopieras. 32
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcpy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcpy( &b [0] [0], &a [0] [0], sizeof( a ) ); a) Förklara returvärdet från memcpy. Returvärdet är en voidpekare (som ska returnera det ursprungliga värdet av dst) 33
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcopy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcopy( &b [0] [0], &a [0] [0], sizeof( a ) ); d) Förklara operatorn sizeof(). Uttrycket sizeof(a) kommer vid kompileringen att bytas ut mot storleken av a uttryckt i antal bytes. Kompilatorn väljer det rätta värdet utifrån vilken typ parametern a har. 34
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcpy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcpy( &b [0] [0], &a [0] [0], sizeof( a ) ); e) Förklara texten (void) vid anropet till memcpy. På returvärdet, vilken typ det än är, görs type-cast till void för att tydligt markera att returvärdet ignoreras. 35
4.6 Funktionen memcpy Bland biblioteksfunktionerna finns en, som heter memcpy, med följande deklaration: void * memcopy( void * dest, const void *src, size_t len ); Ett anrop kan se ut så här: int a[4711] [4711]; int b[4711] [4711]; /* här emellan finns mera kod, som bland annat initialiserar matrisen a */ (void) memcopy( &b [0] [0], &a [0] [0], sizeof( a ) ); f) Visa hur funktionen memcpy kan översättas till assembler för Nios II. Kortare och bättre version: memcpy: mov r2,r4 loop: beq r6, r0,out # kolla om klart ldb r12, 0(r5) # läs en byte addi r5, r5, 1 # öka src-pekare stb r12, 0(r4) # skriv en byte addi r4, r4, 1 # öka dst-pekare subi r6, r6, 1 # minska counter br loop # loopa out: ret 36
4.7 Matriser Visa hur de 15 elementen i nedanstående matriser lagras i minnet. C-kompilatorn lagrar matriser radvis. a) int matris[3][5] [0][0] [0][1] [0][2] [0][3] [0][4] [1][0] [1][1] [1][2] [1][3] [1][4] [2][0] [2][1] [2][2] [2][3] [2][4] a) int annan[5][3] [0][0] [0][1] [0][2] [1][0] [1][1] [1][2] [2][0] [2][1] [2][2] [3][0] [3][1] [3][2] [4][0] [4][1] [4][2] 37
4.8 C-version av puttime Skriv en version av funktionen puttime i C-kod. Deklarationen är void puttime( int * timeloc ); Förslag som kanske kan förbättras: void puttime( int * timeloc ); { register int time = *timeloc; register int ctmp; putchar (0xA); /* ny rad */ putchar (0xD); /* vagnretur */ ctmp = (time & 0xF000) >> 12; ctmp = hexasc (ctmp); putchar (ctmp); } ctmp = (time & 0x 0F00) >> 8; ctmp = hexasc (ctmp); putchar (ctmp); putchar ( : ); ctmp = (time & 0x00F0) >> 4; ctmp = hexasc (ctmp); putchar (ctmp); ctmp = (time & 0x000F); ctmp = hexasc (ctmp); putchar (ctmp); /* kolon*/
register kan användas vid deklaration av lokala variabler Tips till kompilatorn att (om möjligt) använda register i stället för stacken vid hantering av en viss variabel. Det går inte att ha pekare till en variabel som är deklarerad med register. 39
4.9 Struct a) Skriv en C-kod som definierar lagring av en struct tiger med de ingående delvariablerna : namn, 13 tecken, tex tiger (från NallePuh) vikt, avrundat till hela kilogram, tex 245 (som int, dvs 4 bytes) snabbaste fart i kilometer per timme (som float, dvs 4 byte) längd över allt, angiven i meter (som float, dvs 4 byte) struct tiger { }; char char int float float namn[13]; koen; vikt; fart; langd; c) Vilket resultat erhålls som returvärde från operationerna sizeof (struct tiger); troligen (13 + 3) + 4 + 4 + 4 = 28 sizeof (int); troligen 4 40
4.9 Struct a) Skriv en C-kod som definierar lagring av en struct elephant med de ingående delvariablerna : namn, 13 tecken, tex Dumbo (från sagorna) vikt, avrundat till hela kilogram, tex 1243 (som int, dvs 4 bytes) öronyta uppdelat på vänster och höger öra angivet i kvadratmeter (som float, dvs 4 byte) struct oron { float float }; left; right; struct elephant { char namn[13]; int vikt; struct oron oronarea; }; c) Vilket resultat erhålls som returvärde från operationerna sizeof (struct elephant); troligen (13+3) + 4 + 4 + 4 = 28
4.10 Dynamisk minnesallokering med malloc och free Beskriv med ord eller rita en figur, som illustrerar hur minne kan allokeras, avallokeras och återanvändas vid följande tänkvärda operationer i ett C-program. /*1*/ struct elephant * dumbo = malloc(sizeof (struct elephant)); /*2*/ struct tiger * tiger1 = malloc(sizeof(struct tiger); /*3*/ free(dumbo); 28 byte överallt, jämnt delbart med 4 t3 t1 t1 t1 t1 t3 /*4*/ struct tiger * tiger2 = malloc(sizeof(struct tiger)); /*5*/ struct tiger * tiger3 = malloc(sizeof(struct tiger)); /*6*/ free(tiger1); Heap e e t2 t2 t2 1 2 3 4 5 6 lägsta adress här; ökar uppåt i bilden