380 likes | 535 Views
IS1200 Datorteknik, övning 4. Maskinnära programmering med C Förberedelser till hemlaboration 1. 4.1 Little och 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,
E N D
IS1200 Datorteknik, övning 4 Maskinnära programmering med C Förberedelser till hemlaboration 1
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 refererar alltid till .align 2 –minne. • Vid simulering kan man ev. välja annorlunda.
ldw när effektivadresseninte är jämnt delbar med 4 40 41 42 43 adress 40 44 45 46 47 adress 44 * 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
4.3 Minnesdispossition .data .bss .data (initialiserade data) .bss (oinitialiserade data)
4.3 Adressrymd • Programkoden, tex skapad i C-main, C-funktioner eller i assembler-programms .text-segment • hamnar i kod-arean
4.3 Adressrymd • Globala variabler, skapade i början av C-program eller i assemblers .data-segment, som sparas under compile-time, • hamnar i data-arean. • Oinitialiserade globala variabler • hamnar i .bss-delen där.
4.3 Adressrymd • Lokala variabler, skapade i C-main, C-funktion eller i assembler-programs .text-segment • som sparas under run-time • och hamnar på stacken.
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. • Använder man heapen på det sättet, minskar man risken att skriva över information.
4.4 Värden, pekare, referenser, heltalsvariablerSkriv programrader i C som visar hur * och & används för atta) deklarera två heltalsvariabler: a och b C: int a,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
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) 4.4 Värden, pekare, referenser, heltalsvariabler* och &b) initialisera heltalsvariabeln b till värdet 27 i samband med deklarationen enl (a).
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
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 4.4 Värden, pekare, referenser, heltalsvariabler* och &d) tilldela a värdet 4711 och b värdet 47.
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 4.4 Värden, pekare, referenser, heltalsvariabler* och &e) tilldela pekarvariabeln ett värde,så att den pekar på heltalsvariabeln a
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 movia r9, b # nu pekar r9 på b ldw r10, 0(r8) # nu innehåller r10 värdet av ptr1, som pekar på a ldw r11, 0(r10) # nu innehåller r11 värdet som ptr1 pekar på, dvs a stw r11, 0(r9) # nu innehåller b det som ptr1 pekade på, dvs a 4.4 Värden, pekare, referenser, heltalsvariabler* och &f) tilldela heltalsvariabeln b samma värde som a, med hjälp av 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) # nu finns värdet från ptr1 i r10 stw r10, 0(r9) # nu nu har ptr2 samma värde som ptr1. 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: char c, d; Assembler för Nios II: .data .align 2 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. 4.5 Värden, pekare, referenser, char-variablerSkriv 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 atta) deklarera två globala variabler av typen char: c och d.
C: char c; char d = 65; Assembler för Nios II: .data .align 2 c: .byte 0 # 1 byte = 8 bitar med värdet 0 d: .byte 65 # 1 byte = 8 bitar med värdet 65 (dec) 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* 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 (förutom symboliska namn) 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: 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 (istf movia) 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 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: ptr3 = &c; Assembler för Nios II: .txt .align 2 movia r8, c# nu pekar r8 på c movia r9, ptr3# nu pekar r9 på ptr3 stwr8, 0(r9)# nu pekar ptr3 på c 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: 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) # nu innehåller r10 värdet av # ptr3 som pekar på c ldb r11, 0(r10) # nu innehåller r11 värdet som # ptr3 pekar på, dvs c stb r11, 0(r9)# nu innehåller d det som # ptr3 pekade på, dvs c 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: 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) # nu finns värdet från ptr3 i r10 stw r10, 0(r9)# nu har ptr4 samma värde som ptr3 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?
(4.5) Enkel C-kod med pekarea) C-koden nedan ska kompileras för hand till assembler för Nios IIb) Hur översätts raden a = *ptr ?c) Vilket värde returnerar programmet? C: int a; int b = 27; int * ptr; int main() { a = 17; ptr = &b; a = *ptr; return( a ); } assembler: .data .align 2 a: .word 0 a: .word 27 ptr .word 0 .text .align 2 main: # a = 17; movia r8, a movi r9, 17 stw r9, 0(r8) # ptr = &b; movia r8, ptr movia r9, b stw 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
(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 main() { int tmp; ptr = &tmp; *ptr = c; return( tmp ); }
.data .align 2 c: .word 47 ptr: .word 0 .text .align 2 main: # int tmp; # gör plats för tmp på stack addi sp,sp,-4 # ptr = &tmp; movia r8,ptr stw r8, 0(sp) # 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.5) Annan C-kod med pekare
4.6 Funktionen memcpyBland 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ö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.
4.6 Funktionen memcpyBland 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ö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.
4.6 Funktionen memcpyBland 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 ) ); • Förklara returvärdet från memcpy. Returvärdet är en voidpekare (som ska returnera det ursprungliga värdet av dst)
4.6 Funktionen memcpyBland 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 ) ); d) Förklara opreratorn sizeof(). Operatorn sizeof() kommer (at compile time) att ta reda på storleken av a uttryckt i antal bytes.
4.6 Funktionen memcpyBland 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 en void för att därefter ignoreras.
4.6 Funktionen memcpyBland 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 ) ); 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
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] int [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] 4,7 MatriserVisa med en tydlig figur hur de 15 elementen i nedanstående matriser lagras i minnet vid körning av C-programSvar: C-kompilatorn lagrar matriser radvis.
4.8 C-version av puttimeSkriv en version av funktionen puttime i C-kod. Deklarationen ärvoid 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 (’:’); /* kolon*/ ctmp = (time & 0x00F0) >> 4; ctmp = hexasc (ctmp); putchar (ctmp); ctmp = (time & 0x000F); ctmp = hexasc (ctmp); putchar (ctmp); }
register= typmodifierare vid deklaration av automatiska variabler. begäran att (om möjligt) få använda register i CPU istället för stacken vid hantering av viss variabel. CPU:n hanterar ju sina register mycket snabbare än stacken i minnet.
4.9 Structa) 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 namn[13]; int vikt; float fart; float 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
4.9 Structa) 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 left; float 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 freeBeskriv 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 elephant)); /*2*/ struct tiger * tiger1 = malloc(sizeof(struct tiger); /*3*/ free(dumbo); /*4*/ struct tiger * tiger2 = malloc(sizeof(struct tiger)); /*5*/ struct tiger * tiger3 = malloc(sizeof(struct tiger)); /*6*/ free(tiger1); 28 byte överallt, jämnt delbart med 4 1 2 3 4 5 6 t3 t3 t1 t1 t1 t1 t2 t2 t2 e e Heap lägsta adress här; ökar uppåt i bilden