Základy programování 2 KMI/ZP2 Petr Osička KATEDRA INFORMATIKY UNIVERZITA PALACKÉHO V OLOMOUCI
Adresování paměti Adresování paměti Posloupnost bajtů očíslovaných od nuly podobně jako pole. Adresa = index bajtu. Vše v programu je někde v paměti (dokonce i instrukce) a je na nějaké adrese Adresa vícebajtového objektu: adresa prvního bajtu Virtuální paměť: program žije v iluzi toho, že má k dispozici celý adresní prostor, o umisťování do paměti a mapování na fyzickou pamět se stará OS. Adresní rozsah bývá dán velikostí adresy (např. 32 bitů, 64 bitů) Programátor může explicitně zjistit, co je uloženo na dané adrese zjistit, na jaké adrese je daný objekt uložen Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 1 / 33
Typ pro uložení adresy K existujícímu typu x existuje typ adresa hodnoty typu x, např. k typu int existuje typ adresa hodnoty typu int Takovým proměnným říkáme pointer na x, např. pointer na int Typ pointer na typ x označujeme jako x *, typ pointer na int je tedy označován jako int *. Hodnota pointeru je také uložena v paměti, existuje tedy pointer na pointer na x atd. /* * promenne pt a pt2 jsou pointery na int, * promenna h je obycejna promenna typu int. */ int *ptr, *ptr2, h; int a; int *pt1; int **pt2; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 2 / 33
Operátory adresy a dereference int a = 2; int b = 5; /* ziskame adresy promennych a,b */ int *pt_a = &a; int *pt_b = &b; /* pt_c ukazuje take na a */ int *pt_c = pt_a; /* Zmenime hodnotu promenne a tak, ze zapiseme na adresu (pointer pt_c), na ktere je ulozena jeji hodnota. K hodnote promenne b pristupujeme take pomoci adresy (pointer pt_b). Prirazeni je ekvivalentni radku a = 120 + b; */ *pt_c = 120 + *pt_b; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 3 / 33
Program má přístup pouze k paměti, která mu byla přidělěna int *pt = 10; /* nasledujici radek program pravdepodobne shodi */ printf("%i\n", *pt); int *pt_1 = 0; int *pt_2 = NULL; /* test platnosti pointeru pt_1 */ if (!pt_1) { printf("pointer pt_1 obsahujici adresu %p je neplatny!\n", pt_1); /* test platnosti pointeru pt_2 */ if (!pt_2) { printf("pointer pt_2 obsahujici adresu %p je neplatny!\n", pt_2); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 4 / 33
Pole degeneruje na pointer Pole alokované automaticky (= tak jsme to doposud dělali) ve stejném bloku ví o své velikosti. Jinde zdegeneruje na konstatní pointer ukazující na první bajt prvního prvku pole int array[10]; /* spocitame pocet prvku pole * sizeof(array) vrati velikost pole v bajtech!!! * sizeof(array[0]) vrati velikost jednoho prvku v bajtech */ printf("pole ma velikost %lu bajtu a obsahuje %i prvku \n", sizeof(array), sizeof(array)/sizeof(array[0])); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 5 / 33
void test(int array[]) { /* * Tady sizeof(array) vrati velikost pointeru!!! * Tim padem nasledujici radek vypise nespravne hodnoty. */ printf("pole ma velikost %lu bajtu a obsahuje %i prvku \n", sizeof(array), sizeof(array)/sizeof(array[0])); int main() { int array[10]; funkce(array); return 0; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 6 / 33
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10; int a = 15; /* pt obsahuje adresu prvního bajtu prvního prvku pole array */ int *pt = array; int *pt2 = &a; /* zadny z nasledujich tri radku nelze provest, pointer array je konstantni */ array = pt2; array = &a; array = 0; /* vypiseme prvni prvek pole */ printf("%i\n", *pt); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 7 / 33
Pointerová aritmetika K pointeru lze přičíst, nebo od něj odečíst, celé kladné číslo. Výsledkem přičtení čísla c k pointeru pt je pointer, který získáme zvětšením pt o c krát velikost typu, na který pt ukazuje. Pokud by byl například pt pointer na int, tak bychom pt zvětšili o c krát sizeof(int). Odečítání funguje analogicky void print_array_info(int array[], int size) { int i; /* vypiseme index, adresu a hodnotu kazdeho prvku pole */ for(i=0; i<size; i++) printf("index: %i \t address: %p \t value: %i\n", i, array+i, *(array+i)); int main (){ int array[5] = {1,2,3,4,5; print_array_info(array, 5); return 0; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 8 / 33
Pointery a struktury struct my_structure { int item_a; double item_b; ; typedef struct my_structure my_structure; /* pak nekde v kodu */ my_structure data = {10, 3.2; my_structure *pt = &data; /* pristoupime k polozkam struktury data pomoci pointeru pt */ int a = (*pt).item_a; (*pt).item_b = 29.6; /* alternativni syntaxe pomoci sipky*/ int a = pt->item_a; pt->item_b = 29.6; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 9 / 33
Kopírování struktur my_structure data = {10, 3.2; my_structure *pt = &data; /* * tady dojde ke zkopirovani hodnot polozek promenne data * do polozek promenne copy */ my_structure copy = *pt; /* tato zmena se neprojevi v promenne data */ copy.item_a = 123; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 10 / 33
Větší struktury jako parametry funkcí /* misto teto definice funkce */ double fce (my_structure a) { /* pristup k polozkam */ double result = a.item_a + a.item_b; return result; /* pouzijeme radsi tuto definici funkce */ double fce (my_structure *a) { /* pristup k polozkam */ double result = a->item_a + a->item_b; return result; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 11 / 33
Předávání argumentů odkazem int main() { int x, y; x = division(13, 5, &y); /* * v promenne x je 2 * v promenne y je 3 */ Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 12 / 33 int division(int a, int b, int *remainder) { int result = a / b; /* na adresu remainder ulozime zbytek po deleni */ *remainder = a % b; return result;
void pointer a přetypování typové označení void * nelze dereferencovat implicitní přetypování z a na void pointer při přiřazení, předávání argumentů funkcím. využití pro práci s pamětí různých typů, tam kde není potřeba znát typ apod. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 13 / 33
/* mem - adresa pameti, kterou chceme vytisknout size - pocet bajtu, ktere chceme vytisknout bytes_per_row - pocet bajtu, ktere chceme vytisknout na jeden radek */ void dump_memory_on_screen(void *mem, unsigned int size, int bytes_per_row) { /* tady implicitne pretypujeme void pointer na unsigned char */ unsigned char *bytes = mem; int i; for(i=0; i<size; i+=1) { if (i && (i % bytes_per_row == 0)) printf("\n"); /* tisk obsahu pameti hexadecimalne */ printf("%.2x ", *bytes); /* posunu pointer o jeden bajt */ bytes += 1; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 14 / 33
typedef struct { int a; int b; float c; my_struct; int main() { int a = 394301208; double b = 4302.30103; my_struct array[3] = {{1,2,1.5, {3,7,12.2, {1,1,55.4; printf("dumping integer %i\n", a); dump_memory_on_screen(&a, sizeof(a), 8); printf("\ndumping double %f\n", b); dump_memory_on_screen(&b, sizeof(b), 8); printf("\ndumping array of structs\n"); dump_memory_on_screen(array, sizeof(my_struct) * 3, 8); return 0; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 15 / 33
Alokace a dealokace paměti Paměť, kterou běžící program využívá, je mu nejdřívě přidělena (alokována). Pokud jí již není potřeba, je paměť uvolněna. Podle použitého mechanismu přidělování a uvolněnování můžeme paměť rozdělit na dva typy. Na paměť, která je přidělována a uvolňována automaticky, a na paměť, jejíž přidělení a uvolnění musí programátor provést manuálně. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 16 / 33
Automatická alokace a dealokace V tomto režimu jsou globální a lokální proměnné a pole všech typů (včetně struktur!) statické proměnné a pole ve funkcích všech typů Princip fungování Paměť pro globální a statické proměnné a pole je přidělena na začátku běhu programu, předtím než začne běh funkce main. Tato paměť je po přidělení automaticky vynulována. K uvolnění paměti globálních a statických proměnných a polí dochází až na konci běhu programu. Lokální proměnné a pole ve funkcích: V momentu zavolání funkce přidělena paměť pro všechny lokální proměnné, a pro argumenty funkce. Do paměti odpovídající argumentům funkce jsou překopírovány hodnoty skutečně předaných argumentů. Všechna paměť je uvolněna v okamžiku, kdy funkce skončí. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 17 / 33
int *make_array_up_to_five() { int array[10] = {1, 2, 3, 4, 5; /* chyba, po skonceni funkce je pamet uvolnena */ return array; int *bar (int c) { int k = c * 2; /* chyba, po skonceni funkce je pamet uvolnena */ return &k; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 18 / 33
Statické lokální proměnné Proměnnou v těle funkce lze z vyjmout z režimu automatické alokace a dealokace lokálních proměnných a vložit ji do režimu automatické alokace a dealokace globálních proměnných. static typ jmeno = pocatecni_hodnota; Pokud proměnnou při definici neinicializujeme, je inicializována na 0, protože paměť, která je alokována pro globální a statické proměnné, je po alokaci vynulována. Statickou proměnnou můžeme inicializovat pouze konstantní hodnotou vyjádřenou literálem. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 19 / 33
void counting() { static int called = 0; called += 1; printf("pocet zavolani: %i \n"); int main() { int i; /* vypisi se cisla 1 az 10 */ for(i = 0; i < 10; i+=1) counting(); return 0; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 20 / 33
Manuální alokace a dealokace Funkce standardni knihovny (alokátor tam implementovaný). void *malloc(size_t memory_size); Jejím jediným argumentem je počet bajtů, které chceme alokovat. Funkce alokuje souvislý blok paměti a vrátí adresu jeho prvního bajtu. Pokud se alokace paměti z nějakého důvodu nepovede, vrací funkce hodnotu 0. Není definováno, co alokovaná paměť obsahuje. void *calloc(size_t n, size_t unit_size); Bere dva argumenty, počet položek, které chceme alokovat a velikost jedné položky v bajtech. Všechny bajty alokované paměti jsou nastaveny na hodnotu 0. Jinak se chová jako malloc. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 21 / 33
void free(void *ptr); slouží k uvolnění paměti, kterou jsme předtím manuálně alokovali Tuto pamět předáváme funkci pomocí adresy jejího prvníhé bajtu. Pokud je argument jiná adresa není chování funkce definováno. void realloc(void *ptr, size_t memmory_size); změna velikosti alokované paměti argumenty: jsou adresa již (manuálně) alokované paměti a nová velikost. Funkce provede alokace a dealokace nutné pro změnu velikosti paměti a vrátí adresu začátku daného bloku paměti. Tato adresa se může lišit od adresy předané funkci jako argument, může totiž být potřeba paměť přesunout. Pokud pomocí realloc paměť zmenšujeme, je uvolně od konce (jsou uvolněny bajtů s vyššími adresami). Pokud pomocí realloc paměť zvětšujeme, může se stát, je že ji realloc alokuje na novém místě a obsah původní paměti tam překopíruje a paměť na původním místě je uvolněna. Pokud se alokace na novém místě nezdaří, původní paměť uvolněna není a realloc vrací 0. Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 22 / 33
/* alokujeme pole o 10 prvcich */ int *p = malloc(10* sizeof(int)); /* nasledujici pouziti reallocu neosetruje situaci, kdy je nutno pamet presunout a nepovede se nova alokace, to by vedlo ke dvema chybam */ 1) p bude rovno 0 a dal v programu se p muzeme pokusit dereferencovat 2) puvodni pamet nebude uvolne a ztratime na ni adresu -> memory leak p = realloc(p, 20 * sizeof(int)); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 23 / 33
/* spravne musime realloc pouzit nasledovne */ int *tmp = realloc(p, 20*sizeof(int)); if (!tmp) { /* tady se rozhodneme, co provest, pokud se alokace nezdari */ else { p = tmp; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 24 / 33
Příklad 1 int *make_interval(int a, int b) { int size = b - a; int i; int *ret = 0; if (size <= 0) return 0; ret = malloc(size * sizeof(int)); if (!ret) return 0; for(i = 0; i < size; i+= 1) ret[i] = a + i; return ret; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 25 / 33
Příklad 2 /* bag ~ multimnozina prvku pridavani a odebirani prvku testovani poctu vyskytu daneho prvku automaticke zvetsovani, kdyz dojde kapacita */ #define BAG_START_SIZE 100 typedef struct { int *data; int size; int head; bag; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 26 / 33
bag *create_bag () { /* pamet pro structuru bag */ bag *result = malloc(sizeof(bag)); if(!result) return 0; /* pamet pro pole pro uchovavani prvku */ result->data = malloc(sizeof(int) * BAG_START_SIZE); if(!result->data) { free(result); return 0; result->size = BAG_START_SIZE; result->head = 0; return result; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 27 / 33
/* vraci 1, kdyz se pridani povede vraci 0, kdyz se pridani nepovede */ int add_to_bag (bag *b, int element) { if (b->head == b->size) { int *tmp; b->size *= 2; tmp = realloc(b->data, b->size * sizeof(int)); if(!tmp) return 0; b->data = tmp; b->data[b->head] = element; b->head += 1; return 1; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 28 / 33
void delete_from_bag (bag *b, int element) { int i; /* hledam prvek */ for(i = 0; i < b->head; i += 1){ if(b->data[i] == element) break; /* pokud jsem prvek nasel, tak jej smazu vymenou s poslednim prvkem a snizenim head o 1 */ if(i!= b->head){ b->data[i] = b->data[b->head-1]; b->head -= 1; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 29 / 33
int occurence_in_bag (bag *b, int element) { int i; int occurences = 0; for(i=0; i<b->head; i+=1){ if (b->data[i] == element) occurences += 1; return occurences; Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 30 / 33
Další funkce pro práci s pamětí void *memcpy(void *mem1, void *mem2, size_t n); zkopíruje n bajtů z adresy mem2 na adresu mem1 a vrátí mem1, pamět se nesmí překrývat. na adrese mem1 musí být alokována dostatečně velká paměť a na adrese mem2 musí být dostatečně mnoho bajtů pro čtení V opačném případě je chování funkce nedefinováno a může vést k havárii programu. /* kopirovani pole: predpokladejme, ze source a target jsou pole typu int o size prvcich. */ /* kopie prvku cyklem */ int i; for(i=0; i<m; i+=1) target[i] = source[i]; /* kopie pomoci memcpy */ memcpy(foo, bar, sizeof(int) * m); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 31 / 33
void *memmove(void *mem1, void *mem2, size_t n); Memmove funguje stejně jako memcpy, ovšem s tím rozdílem, že kopírování proběhne správně i v případě, kdy se zdrojová a cílová pamět překrývají. void *memset(void *mem, int c, size_t n); Jako parametry se funkci předává adresa začátku paměti, hodnota, na kterou chceme bajty nastavit, a počet bajtů, které chceme nastavit. Cílová hodnota se předává funkci jako int, ve funkci je přetypována na unsigned char. Funkce vrací pointer mem. /* vynulovani pole array o velikosti m a typu int)*/ memset(array, 0, sizeof(int) * m); Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 32 / 33
Pitfalls Memory leaking: alokujeme paměť a pak ji nedealokujeme (a často navíc zapomeneme její adresu). Dvojí dealokace: použijeme free na adresu jiz dealokované paměti int *ptr = 0; /* do stuff... */ if (ptr) { free(ptr); ptr = 0; Zodpovědnost: kdo alokuje a kdo dealokuje, vlastnictví paměti (objektů), životní cyklus paměti atd. Nástroje kontrolující chování paměti za běhu, např. Valgrind Osička (Univerzita Palackého v Olomouci) ALS2 LS 2019 33 / 33