Grundläggande C-programmering del 3 Ulf Assarsson Läromoment: Grundläggande C-programmering del 3 enum, union, byte-adressering med unions, include-guards Pekare och arrayer, 2D-arrayer dubbelpekare Kopplat.ll: Lab 5 - spelprogrammering Läsanvisningar: Översiktligt: STM32F407 reference manual s: 951-1002
C synlighet för deklara.oner Alla deklara.oner (variabler, funk.oner) och även usryck som typedefs + defines är synliga först nedanför dem ej ovanför dem. Källkodsfiler processas uppifrån och ned. Exempel: void fkn1(short param) if( ) return fkn2('a'); // fkn2 är här ännu okänd för C-kompilatorn så detta ger // kompileringsfel. return; void fkn2(char c) return; FortsäSning Pekare/VK 2
C synlighet för deklara.oner Alla deklara.oner (variabler, funk.oner) och även usryck som typedefs + defines är synliga först nedanför dem ej ovanför dem. Källkodsfiler processas uppifrån och ned. Exempel: void fkn2(char c); // Fix för att göra deklarationen av fkn2 känd redan här. // Vi får ha med deklarationen hur många ggr vi vill void fkn1(short param) void fkn2('a'); // Vi kan även lägga den här istället if( ) void fkn2('a'); // eller här return fkn2('a'); // Här är nu fkn2 känd return; void fkn2(char c) // Definitionen av fkn2 return; FortsäSning Pekare/VK 3
Preprocessor direc.ves - #if, #ifdef, #ifndef Preprocessor condi.onal inclusion #define X 1 // syntax: #define [identifier name] [value], där [value] är optional #if X == 0 // syntax: #if <value>, där 0=false och!0==true // any C-code #elif X-1 == 1 // betyder else if #else #endif #if 0 // bra för att temporärt kommentera bort stora block av kod. // any C-code #endif #define HW_DEBUG #ifdef HW_DEBUG // any C-code, t ex: #undef SIMULATOR #endif void delay_500ns(void) #ifndef SIMULATOR delay_250ns(); delay_250ns(); #endif FortsäSning Pekare/VK 4
Enumera.ons enum enum type_name value1, value2,..., valuen ; // type_name optional. By default, value1 = 0, value2 = value1 + 1, etc. enum type_name value1 = 0, value2, value3 = 0, value4 ; // helt OK. // Ger dock med gcc värdena: 0, 1, 0, 1 vilket kanske är ointuitivt. enum day monday=1, tuesday, wednesday, thursday, friday, saturday, sunday; enum day today; today=wednesday; printf("%d:th day",today+1); // output: "4:th day" typedef enum false, true bool; bool ok = true; --------------------------------------------------------------------------------- #define B_E 0x40 // GPIO-portexemplet med enums #define B_RST 0x20 #define B_CS2 0x10 #define B_CS1 8 #define B_SELECT 4 #define B_RW 2 #define B_RS 1 graphic_ctrl_bit_clear( B_RS B_RW ); Kan bytas ut mot: enum B_RS=1, B_RW=2, B_SELECT=4, B_CS1=8, B_CS2=0x10, B_RST=0x20, B_E=0x40; 5
Bonus Type Union Union allows to store different data types in the same memory loca.on. Same syntax as struct union opt_name int a; char b; float c; x; x.a = 4; x.b = 'i'; x.c = 3.0; &x.a == &x.b == &x.c a, b och c delar samma minnesadress. D v s samma minnesadress kan adresseras på tre olika säs via tre olika variabelnamn. typedef union float v[2]; struct float x,y;; Vec2f; Vec2f pos; pos.v[0] = 1; pos.v[1] = 2; pos.x = 1; pos.y = 2; Exempel: pos.v[0] och pos.x är samma sak. pos.x visar tydligt as det är x-koordinaten vi adresserar. pos.v[i] är dock användbart om vi vill skriva en loop över x- och y-koordinaten. Tex: Vec2f addvec(vec2f a, Vec2f b) for(i=0; i<2; i++) a.v[i] += b.v[i]; return a; 6
Endian Big endian / LiSle endian Vi använder lisle endian. ARM Cortex-m4 klarar båda. Kodningskonven.oner/VK 7
Byte-adressering med unions för GPIO-porten // GPIO typedef struct _gpio uint32_t moder; uint32_t otyper; uint32_t ospeedr; uint32_t pupdr; union uint32_t idr; struct byte idrlow; byte idrhigh; ; ; union uint32_t odr; struct byte odrlow; byte odrhigh; ; ; GPIO; #define GPIO_D (*((vola.le GPIO*) 0x40020c00)) #define GPIO_E (*((vola.le GPIO*) 0x40021000)) GPIO Input Data Register (IDR) offset 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 mnemonic 0x10 RESERVERADE r r r r r r r r r r r r r r r r IDR 0x11 0x10 Bitar 16 tom 31 används inte och ska hållas vid sitt RESET-värde, dvs 0. Nu kan idrhigh adresseras med: byte c = GPIO_E.idrHigh; Istället för med: byte c = *((byte*)&(gpio_e.idr) + 1)); ES.ll exempel: GPIO_E.odrLow &= ( ~B_SELECT & ~x); Istället för: *((byte*)&(gpio_e.odr)) &=(~B_SELECT & ~x); FortsäSning Pekare/VK 8
Include guards Include guards används för as automa.skt undvika as inkludera en.h-fil mer än en gång per.c-fil. DeSa kan annars hända om en.c-fil inkluderar flera.h-filer som i sin tur inkluderar samma.h-fil. FortsäSning Pekare/VK 9
Include guards // main.c #include "player.h" #include "enemies.h" // vecmath.h #ifndef VECMATH_H #define VECMATH_H // player.h #ifndef PLAYER_H #define PLAYER_H // enemies.h #ifndef ENEMIES_H #define ENEMIES_H void main() typedef struct union int v[2]; struct int x, y;; ; Vec2i; #include "vecmath.h" void moveplayer(vec2i v); #endif //PLAYER_H #include "vecmath.h" void moveenemy(int i, Vec2i v); #endif //ENEMIES_H Vec2i add2i(vec2i a, Vec2i b); c-fil char isequal(vec2i a, Vec2i b); #endif //VECMATH_H Vi bör ha include-guards på alla.h-filer även om det i desa exemplet inte behövs för player.h samt enemies.h Utan include guards så inkluderas vecmath.h två gånger för main.c. Andra gången kommer kompilatorn klaga på as Vec2i redan är definierad..c-filer kompileras var för sig. Preprocessorn och kompilatorn exekveras individuellt per.c-fil. 10
Bonus C non-standard extensions Följande finns som extensions.ll C Stöds typiskt inte alls i C++ Dessa stöds i C99 men typiskt ej i C89 / C11 (möjligen som op.onal för kompilaror-.llverkaren). FortsäSning Pekare/VK 11
C nested/local func.ons are compilerop.onal extensions to C Bonus VC++ (dvs Visual Studio C++) och GNU C++ stödjer inte lokala funk.oner. Det gör däremot ARM-gcc och MinGW-gcc. void fkn(double a, double b) double square (double z) return z * z; return square (a) + square (b); FortsäSning Pekare/VK 12
C nested/local func.ons are compilerop.onal extensions to C Bonus VC++ (dvs Visual Studio C++) och GNU C++ stödjer inte lokala funk.oner Det gör däremot ARM-gcc och MinGW-gcc. void main() void fkn(int *array, int offset, int size) int access (int *array, int index) return array[index + offset]; int i; for (i = 0; i < size; i++) access (array, i); int a[] = 1,2,3,4,5, offs=0, size = 5; fkn(a, offs, size); Den lokala funk.onen har t.o.m..llgång.ll omgivande scope s hizlls deklarerade variabler. FortsäSning Pekare/VK 13
Bonus C99 variable-length arrays (Op.onal in C11) void fkn(int len) char str1[10]; // längd känd i compile time. char str2[len]; // längd ej känd i compile time men i run time. str2 är en variable-length array (C99) FortsäSning Pekare/VK 14
Bonus C99 struct ini.a.on med.medlem, Initiering med.medlem =... tillåter oss att endast initiera valfria medlemmar. Exempel: struct Tst int a; char b; ; struct Tst x1 =.b = 'z' ; // ofullständig initiering struct Tst x2 =.b = 'z,.a = 5 ; // initiering i valfri ordning struct Tst x3 =.a = 5,.b = 'z' ; // initiering i valfri ordning FortsäSning Pekare/VK 15
Varför pekare? #include <stdlib.h> void inc(int x, char y) x++; y++; Några exempel: Skriva.ll / Läsa från portar Indexera fortare i arrayer Slippa kopiera inputparametrar Ändra inputparametrarna Pekare.ll funk.oner -> objektorienterad s.l #include <stdlib.h> void inc(int *x, char *y) (*x)++; (*y)++; Argumenten är pass-by value i C. int var1 = 2; char var2 = 7; inc(var1, var2); var1 och var 2 har for arande värdena 2 resp 7 eer funk.onsanropet Argumenten är pass-by value i C. int var1 = 2; char var2 = 7; inc(&var1, &var2); var1 och var 2 har nu värdena 3 resp 8 eer funk.onsanropet Introduk.on C-programmering 16
Array - Likhet med pekare Har en adress och en typ. char s2[] = "Emilia"; - sizeof(s2) == 7 - Men sizeof(char*) == 4; Indexering har samma resultat. char* s1 = "Emilia"; char s2[] = "Emilia"; s1[0] = E ; s2[0] = E ; *s1 == E ; *s2 == E ; // eersom s2 är adress så kan vi dereferera den // precis som för en pekare Pekare och Arrayer/VK 17
Indexering #include <stdio.h> char* s1 = "Emilia"; char s2[] = "Emilia"; int main() // tre ekvivalenta sätt att dereferera en pekare printf("'l' i Emilia (version 1): %c \n", *(s1+3) ); printf("'l' i Emilia (version 2): %c \n", s1[3] ); printf("'l' i Emilia (version 3): %c \n", 3[s1] ); return 0; x[y] översäss.ll *(x+y) och är alltså es säs as dereferera en pekare. Pekare och Arrayer/VK 18
char* s1 = "Emilia"; char s2[] = "Emilia"; Skillnader mellan array och pekare Arrayer s2 är symbol och kan ej ändra värde. Värdet är s2 s adress i minnet. Adress känd i compile-.me. Storlek känd i compile-.me. Storlek för pekare är storlek på adress vilket är 4 bytes på 32-bitssystem. Pekar-aritme.k möjligt men ej as försöka ändra symbolens värde. char a[] = hej ; a++ ej OK (a+1)[0] helt OK. a är en symbol för adressen.ll h. char* p = a; p är en variabel som går as ändra och här säss.ll a, dvs adressen för h. Pekare och Arrayer/VK 19
Array (Fält) #include <stdio.h> #include <conio.h> char * s1 = "Emilia"; // s1 är pekare. Variabeln s1 är en variabel som går att ändra, och vid start tilldelas värdet av adressen till 'E : char s2[] = "Emilia"; // s2 är array. Värdet på symbolen s2 är känt vid compile time. Symbolen s2 är konstant, dvs ingen variabel som går att ändra. Är adressen till 'E. int main() // tre ekvivalenta sätt att dereferera en pekare printf("'l' i Emilia (version 1): %c \n", *(s1+3) ); printf("'l' i Emilia (version 2): %c \n", s1[3] ); printf("'l' i Emilia (version 3): %c \n", 3[s1] ); // pekararitmetik går även utmärkt för s2 printf("'l' i Emilia (version 3): %c \n", *(s2+3) ); printf("'l' i Emilia (version 3): %c \n", (s2+3)[0] ); char a[] = "hej"; (a+1)[0] = 'o'; char* p = a; p = "bye"; // funkar. Strängen bye" allokeras i compile time i strängliteralminne. char b[10]; // b blir 10 element stor och får adressvärde // b = "då"; // här försöker vi ändra b's värde och det GÅR inte. printf("%s\n", p); return 0; 20
Arrayer som funk.onsargument blir pekare void foo(int pi[]); void foo(int *pi); [ ] nota.onen finns, men betyder pekare! Undviker as hela arrayen kopieras. Längd inte all.d känd i compile.me. Adressen.ll arrayen läggs på stacken och accessas via stackvariabeln pi. (En struct kopieras och läggs på stacken). Pekare och Arrayer/VK 21
Antal bytes med sizeof() #include <stdio.h> char* s1 = "Emilia"; char s2[] = "Emilia"; sizeof är en inbyggd operator i C som returnerar storlekten hos typen i antal bytes. int main() printf("sizeof(char): %i \n", sizeof(char) ); printf("sizeof(char*): %i \n", sizeof(char*) ); printf("sizeof(s1): %i \n", sizeof(s1) ); printf("sizeof(s2): %i \n", sizeof(s2) ); return 0; sizeof(char): sizeof(char*): sizeof(s1): sizeof(s2): 1 4 4 7 Sizeof utvärderas i compile-.me. En (av få) undantag där arrayer och pekare är olika. Pekare och Arrayer/VK 22
Array av pekare #include <stdio.h> char* fleranamn[] = "Emil", "Emilia", "Droopy"; int main() printf("%s, %s, %s\n", fleranamn[2], fleranamn[1], fleranamn[0]); return 0; Droopy, Emilia, Emil sizeof(fleranamn) = 12; // 3*sizeof(char*) = 3*4 = 12 Pekare och Arrayer/VK 23
Array av arrayer #include <stdio.h> char kortanamn[][4] = "Tor", "Ulf", "Per"; int main() printf("%s, %s, %s\n", kortanamn[2], kortanamn[1], kortanamn[0]); return 0; Per, Ulf, Tor sizeof(kortanamn) = Pekare och Arrayer/VK 24
Array av arrayer #include <stdio.h> int arrayofarrays[3][4] = 1,2,3,4, 5,6,7,8, 9,10,11,12 ; int main() int i,j; for( i=0; i<3; i++) printf("arrayofarray[%i] = ", i); for ( j=0; j<4; j++) printf("%i ", arrayofarrays[i][j]); printf("\n"); return 0; FortsäSning Pekare/VK 25
Pekare.ll pekare (dubbelpekare) char *p1, *p2, *p3; char **pp; pp = &p1; Pointer Pointer Variable address address value // Annat exempel. Funktion som allokerar minne dynamiskt, t ex för elaka fiender i ett spel void allocateenemyarray(struct Enemy **pp, int n) *pp = (struct Enemy *)malloc(n * sizeof(struct Enemy)); int main() struct Enemy *penemies = NULL; allocate(&penemies, 100); // Game logic free(penemies); Ska funk.onen kunna uppdatera argumentet måste vi skicka in adressen för argumentet (istället för värdet på argumentet) Jämför med: void inc(int *x, char *y) (*x)++; (*y)++; int var1 = 2; char var2 = 7; inc(&var1, &var2); 26
Pekare.ll pekare (dubbelpekare) #include <stdio.h> #include <conio.h> char* s1 = "Emilia"; // variabeln s1 är en variabel som går att ändra, och vid start tilldelas värdet av adressen till 'E': char s2[] = "Emilia"; // värdet på s2 känt vid compile time. s2 är konstant, dvs ingen variabel som går att ändra. Är adressen till 'E. int main() char** pp; char* p = s1; // == &(s1[0]) pp = &p; *pp = s2; // ändrar p till s2 **pp = 'A'; // ändrar s2[0] till 'A' printf("%s\n", p); return 0; Pekare och Arrayer/VK 27
Dubbelpekare. Exempel int main() char s1[] = "Emilia"; char **pp, *p; -- MODIFIERA s1 via dubbelpekaren pp--- printf("s1 = %s", s1); return 0; Om ni är klara gör trippelpekare etc Kodningskonven.oner/VK 28
Dubbelpekare. Lösning int main() char s1[] = "Emilia"; char **pp, *p; p = &s1[0]; // p = adressen till 'E' // eller p = s1; // dvs p pekar på arrayen s1. pp = &p; **pp = 'A // eller (*pp)[0] = 'A'; *(*pp + 2) = 'e'; // pp pekar på pekaren p // dubbel avreferering av "pp som pekar på p som pekar på 'E'". // *pp pekar på p. Så det blir: p[0] = 'E' // *(p + 2). Dvs innehållet i (p + 2) tilldelas 'e. printf("s1 = %s", s1); getch(); return 0; Kodningskonven.oner/VK 29
Övningsuppgier om ni vill Skriv ned en struct Definiera en struct. Accessa en structmedlem via en pekare till structen. Dvs -> -notation. Skriv en funktion som clear:ar en variabel (sätter den = 0) så att man kan skriva t ex int a; clear(a); Skriv ned en union (som innehåller en enum) Vad är skillnaden mellan char* str = hej ; resp. char str[] = hej ; Skapa en funktionspekare till en funktion som tar en int-parameter och returnerar en float. Anropa funktionen och tilldela värdet till variabel a. 30