Priama práca s pamäťou

Pamäť RAM

  • vieme si ju predstaviť ako niekoľko "nádob", kam si môžme niečo uložiť.
  • každá "nádoba" má meno a číslo.
  • čísla nádob idú podľa poradia.

Pamäť RAM

  • Mená a typy premenných určuje programátor.
  • Adresy premenných určuje operačný systém.
    • ale je možné ich zistiť a pracovať s nimi.

Pamäť RAM

Vytvoríme niekoľko premenných:

char znak = 'a';
char pole[5] = "ahoj";

Operačný systém určí, na ktorom mieste RAM sa budú nachádzať.

Pamäť RAM

adresa:   \#10         \#15                        \#19
        +----------------------------------------------+
obsah:  | 'a' | 4 | 5 | 'a' | 'h' | 'o' | 'j' | 0  | 5 |
        +----------------------------------------------+

rozsah: |<--->|       |<-------------------------->| 

typ:    char          char[5]
názov:  znak          pole

Smerník

Adresu premennej nazývame aj smerník.

Tajomstvo smerníkov je tajomstvo programovania

Smerník == Adresa

  • Adresa je celé číslo.
  • Celé číslo si vieme uložiť do premennej.

Smerníková premenná

Premenná, do ktorej si uložíme smerník.

int premenna_typu_int = 2;
int* adresa_premennej_typu_int = &premenna_typu_int;

operátor & vráti adresu premennej

adresu si môžme poznačiť do smerníkovej premennej

Smerníková premenná

adresa:   \#10              \#15              \#19
        +--------------------------------------------+
obsah:  | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 10 | 0 | 0 |
        +--------------------------------------------+

rozsah: |<------------->|   |<-------------->| 

typ:    int                 int*
názov:  premenna_typu_int   adresa_premennej_typu_int

Smerníková premenná vždy ukazuje na miesto určitého typu.

  • int* adresa premennej typu int.
  • char* adresa premennej typu char.
  • void* ľubovoľná adresa.

Typ smerníkovej premennej môžme ľubovoľne meniť

int a = 2;
void* pa = &a;
int* ppa = (int*)pa;

Za behu je možné meniť dátové typy.

Operátor dereferencie *

Vráti hodnotu ktorá je na adrese.

Smerníkovú premennú vieme používať ako obyčajnú premennú

Ale musíme použiť operátor dereferencie

Smerníkovú premennú vieme používať ako obyčajnú premennú

int premenna_typu_int = 2;
// Smerníkový typ
int* adresa_premennej_typu_int = &premenna_typu_int;
// Operátor dereferencie
printf("Tam kde ukazuje smernik je hodnota %d\n",*adresa_premennej_typu_int);

Pomocou dereferencie je možné meniť hodnotu

int premenna_typu_int = 2;
// Smerníkový typ
int* adresa_premennej_typu_int = &premenna_typu_int;
// Operátor dereferencie na lavej stane
*adresa_premennej_typu_int = 3;
`*` má dva významy - na druhom riadku je súčasťou označenia smerníkového typu, v treťom riadku označuje dereferenciu.

Polia a smerníky

Názov poľa je adresou jeho prvého prvku.

Ľubovoľná adresa môže byť začiatkom poľa.

ZEN jazyka C

Názov poľa je adresou jeho prvého prvku.

Ľubovoľná adresa môže byť začiatkom poľa.

Polia a smerníky

int pole[5] = {1,2,3,4,5};
int* zaciatok_pola = pole;
for(int i = 0; i < 5; i++){
    if (pole[i] == zaciatok_pola[i]){
        printf("Sicko v poriadku\n");
    }
    else {
        printf("Ta...\n");
    }
}

Operácie so smerníkmi

  • Čo sa nachádza na vedľajšom mieste?
  • Čo sa nachádza na ľubovoľnej adrese?

Smerníková aritmetika

int pole[5] = {1,2,3,4,5};
int* zaciatok_pola = pole;
int* druhe_miesto = pole + 1;
int* tretie_miesto = pole + 2;
int* piate_miesto = tretie_miesto + 2;

Smerníková aritmetika je iný zápis [] a &

int pole[5] = {1,2,3,4,5};
int* zaciatok_pola = &pole[0];
int* druhe_miesto = &pole[1];
int* tretie_miesto = &pole[2];
int* piate_miesto = tretie_miesto[2];

Ako spracovať ľubovoľné množstvo dát?

Potrebujeme ľubovoľné množstvo pamäte.

Ako počas behu programu ovládať množstvo dostupnej pamäte?

  • Statická alokácia: Ak presne vieme koľko pamäte budeme potrebovať.
  • Dynamická alokácia: Ak chceme množstvo meniť počas behu programu.

K dispozícii máme dva druhy pamäte

  • kopa (heap)
  • zásobník (stack)

Zásobník

  • Staticky alokovaná pamäť.
  • Pre lokálne premenné.

Kontext funkcie

  • Pri volaní funkcie sa automaticky vyhradí pamäť pre lokálne premenné.
  • Do "kontextu" sa uložia lokálne premenné.
  • Keď funkcia skončí, pamäť sa uvoľní.
+---------+
| main()  |
| int pr1 |
|    +------------+
|    | funkcia1() |
|    | int pr2    |
|    | +---------------------+
|    | | funkcia2(int arg1)  |
|    | | float pr3           |
|    | +---------------------+
|    |            |
|    | +---------------------+
|    | | funkcia3(int arg1)  |
|    | | float pr3           |
|    | +---------------------+
|    |            |
|    +------------+
|        |
+--------+

Obsah statickej pamäte sa odovzdáva kopírovaním

void funkcia(int a) {
     a = 2;
}
int main(){
    int a = 1;
    funkcia(a);
}

Dynamická alokácia pamäte (na kope)

Keď dopredu nevieme koľko pamäte budeme potrebovať.

Napr. potrebujeme prečítať súbor po riadkoch.

Pri statickej alokácii budeme vždy obmedzení max. veľkosťou riadku, alebo budeme veľmi plytvať.

O kopu sa stará programátor

Existuje jedna "kopa" počas celého behu programu.

Na konci ju (možno) zruší operačný systém.

Využitie kopy

  • Alokuje sa na požiadanie pomocou malloc() alebo calloc().
  • Uvoľňuje sa na požiadanie pomocou free().
  • Je dostupná pomocou smerníka.

Malloc

Vyhradili sme si 100 bajtov pamäte a ich adresu sme uložili do premennej mojapamat.

void* mojapamat = malloc(100);

Smerník typu void* je všeobecný smerník, ktorý nehovorí na aký typ pamäte ukazuje.

Free

free(mojapamat);

Každú vyhradenú pamäť musíme explicitne uvoľniť pomocou free.

Pretypovanie

int* intpamat = (int*)mojapamat;

Mám dosť pamäte?

intpamat[10] = 1;

Mám dosť pamäte?

// Mam k dispozicii 20 miest typu int
int* intpamat = malloc(sizeof(int) * 20);
intpamat[10] = 1;
free(intpamat);

Dynamicky alokovaná pamäť je dostupná iba pomocou smerníkovej premennej

Prvé miesto:

int prvahodnota = *intpamat;

Tretie miesto:

int tretiahodnota = intpamat[2];

Ako to vyzerá v pamäti?

                   +-------------------+
Pamäť na kope      | | | | | | | | | | |
                   +-------------------+
adresa:          \#10 1              \#20
                   ^
                   |
                   +---------------------------+
                                               |
Pamäť na stacku                                |
                                               |
názov premennej:   poleznakov                  |
typ premennej:      char*                      |
                   +--------------+            |
hodnota:           |     10       | -----------+
                   +--------------+
adresa:             \#100        \#108

Alokácia a inicializácia dynamickej pamäte

char* poleznakov = (char*)calloc(1,100);
poleznakov[0] = 'a';
free(poleznakov);

Zmena veľkosti alokovanej pamäte: realloc

char* poleznakov = (char*)calloc(1,100);
poleznakov[0] = 'a';
poleznakov = realloc(poleznakov,200);
poleznakov[150] = 'b';
free(poleznakov);

Funkcia so smerníkovým argumentom

  • Vstupný argument funguje ako výstupný.
  • Menej kopírovania.

Funkcia so smerníkovým argumentom

Môže spracovať aj reťazec:

int strlen(char* str){
    int l = 0;
    while(*str != 0){
        l++;
    }
    return l;
}

Reťazec je pole znakov zakončené nulou.

Funkcia so smerníkovým argumentom

int maximum(int* pole, int sz){
    int max = -1000000;
    for (int i = 0 ; i < sz; i++){
        if (pole[i] > max){
            max = pole[i];
        }
    }
    return max;
}

Funkcia so smerníkovým argumentom

Môže načítať a hlásiť výsledok načítania.

int nacitaj_int(int* vysledok);

Funkcia so smerníkovým argumentom

// Vrati kladne cislo 
// ak sa nacitanie podarilo
// Ak nie vypise spravu a vrati nula
int nacitaj_int(int* vysledok){
 int r = scanf("%d",vysledok);
 if (r!=1){
     printf("Nepodarilo sa.\n");
     return 0;
 }
 return 1;
}

Funkcia so smerníkovým argumentom

Umožňuje pracovať s ľubovoľnými dátami

int compare(const void* p1,const void* p2){
    // Zmením typ smerníka
    int* pa = (int*)p1;
    int* pb = (int*)p2;
    // Skopírujem hodnoty
    int a = *pa;
    int b = *pb;
    return b - a;
}

Argument funkcie sa vždy kopíruje

// Toto nefunguje
int nacitaj_int(int vysledok){
// Vysledok je lokalna premenna
 int r = scanf("%d",&vysledok);
 if (r!=1){
     printf("Nepodarilo sa.\n");
     return 0;
 }
 // Ked skoncim, vysledok sa zabudne
 return 1;
}

Sme zodpovední za to, že pracujeme s vyhradenou pamäťou

int pole[5] = {1,2,3,4,5};
int* neplatne_miesto = pole + 10;
printf("Hodnota %d\ je nedefiovana, lebo adresa %p mi nepatri\n",*neplatne_miesto,neplatne_miesto);

Sme zodpovední...

(Skoro) nikto Vás neupozorní, že pracujete s nedefinovanou hodnotou.

Len program sa správa nepredvídateľne.

Sme zodpovední...

Používame Valgrind, upozorní nás na problémy s pamäťou.

Valgrind je Váš priateľ

Aj keď sa veľa sťažuje....

Vďaka smerníkom vieme

  • menej kopírovať.
  • používať vstupný argument na uložene výsledku.
  • vytvárať funkcie pre modifikovanie obsahu premennej.
  • vytvárať funkcie pre prácu s poľom.
  • vyhradiť si na kope práve toľko pamäte, koľko potrebujeme.
  • zmeniť veľkosť vyhradenej pamäte pomocou realloc.
  • uvoľniť vyhradenú pamäť pomocou free.
Reload?