11. díl - Pokročilé operace s pamětí v jazyce C

C++ Dynamická práce s pamětí Pokročilé operace s pamětí v jazyce C

Z minulých lekcí již umíme dynamicky alokovat paměť a pracovat s ní alespoň an základní úrovni. V dnešním dílu se blíže podíváme na některé funkce, které nám standardní knihovna nabízí a také se ještě jednou podíváme na ukazatele.

Řetězení ukazatelů

Dosud jsme pracovali pouze s jednoduchými ukazateli. Jednoduše jsme alokovali blok paměti a brali ho jako pole. Co ale dělat, když budeme chtít mít např. pole polí? Bez dynamické alokace by byl postup jednoduchý:

int vicerozmerne_pole[25][25];

Pomocí dynamické alokace to ale tak snadné nebude. Nejdříve se podívejme na řetězení ukazatelů. Víme, že ukazatel ukazuje na místo v paměti. Co na takovém místě může být uloženo? Prakticky cokoliv - a to včetně dalšího ukazatele. Podívejme se na menší ukázku.

int cislo = 5;
int* ukazatel = &cislo; // známe z předchozích článků
int** ukazatel_na_ukazatel = &ukazatel; // toto je nové
**ukazatel_na_ukazatel = 4; // změníme původní hodnotu

Bude program fungovat? Samozřejmě. Interně si můžeme ukazatel představit jako pouhé číslo. Toto číslo říká, na kterou adresu (místo) v paměti se má program obracet. Hvězdičkou před názvem proměnné řekneme, že nechceme použít hodnotu na tomto místě, ale až v přesměrovaném místě. Tím jsme opět dostali nějakou hodnotu. A když přidáme ještě jednu hvězdičku, jsme opět přesměrováni. Toto přesměrování (tedy získání hodnoty, na kterou ukazatel ukazuje) se nazývá dereferencování ukazatele.

Pojďme si tedy vytvořit vícerozměrné pole, které bude celé uloženo na haldě. Nejprve dynamicky alokujeme pole ukazatelů. Na tyto ukazatele následně alokujeme jednotlivé prvky pole. Nakonec pole naplníme a vypíšeme si hodnotu na určených souřadnicích.

#define SIRKA 5
#define VYSKA 12

int main(int argc, char** argv) {
    int i, j;
    // vytvoreni pole
    int** pole = (int**)malloc(sizeof(int*) * VYSKA);
    for(i=0; i < VYSKA; i++)
        pole[i] = (int*)malloc(sizeof(int) * SIRKA);
    // naplneni pole
    for(i=0; i < VYSKA; i++)
        for(j = 0; j < SIRKA; j++)
            pole[i][j] = i * 10 + j;
    // vypsani hodnoty
    printf("Hodnota na souradnicich [8,2] je %d", pole[8][2]);
    // smazani pole
    for(i = 0; i < VYSKA; i++)
        free(pole[i]);
    free(pole);
    return (EXIT_SUCCESS);
}
Vicerozměrné pole v C

Jednorozměrné pole simulující vícerozměrné

Místo vytvoření opravdového vícerozměrného pole bychom mohli alternativně vytvořit pole jednorozměrné a "souřadnice" v něm přepočítávat tak, aby se jednotlivé řádky ukládaly za sebe, do jednoho řádku, které pole tvoří. Postup by byl celkem neohrabaný a vytváření a mazání pole bylo složité. Je-li vytvářené pole čtvercové, lze souřadnici snadno dopočítat. To demonstruje následující kód:

// vytvoreni pole
int* jednorozmerne_pole = (int*)malloc(sizeof(int) * VYSKA * SIRKA);
// plneni pole
for(i = 0; i < VYSKA; i++)
    for(j = 0; j < SIRKA; j++)
        jednorozmerne_pole[i * SIRKA + j] = i * 10 + j;
// vypsani hodnoty
printf("Hodnota na souradnicich [8,2] je %d\n", jednorozmerne_pole[8 * SIRKA + 2]);
// smazani pole
free(jednorozmerne_pole);

Výhoda je jednodušší vytváření a mazání pole. Nevýhodou je neustále dopočítávání skutečného indexu. Čím více by mělo pole dimenzí, tím složitější by výpočet byl.

Realokace paměti

Paměť tedy umíme dynamicky vytvořit a uvolnit. Jazyk C však poskytuje ještě další dvě funkce, kterými si lze zjednodušit práci. První z nich je realloc(). Jako první parametr přijímá starý ukazatel a jako druhý nový počet bajtů, které má alokovat. Funkce dokáže zvětšit nebo zmenšit již existující pole. Chceme-li pole zvětšit, tak se realloc() podívá, jestli je za naším polem dostatek místa. Pokud ano, tak aktuálně vyhrazené místo zvětší. Pokud není, alokuje nové místo s dostatkem prostoru a původní obsah pole překopíruje do nového, přičemž staré pole smaže. Původní ukazatel již tedy nemusí být validní a musí se pracovat s ukazatelem navráceným!

Z toho je snadné odvodit základní použití:

pole = (int*)realloc(pole, sizeof(int) * 10);

Druhou funkcí je calloc(). Vytvoří nové pole, ale všechny bity nastaví na 0. Pozor, nemá klasickou deklaraci. Prvním parametrem je počet prvků, které chceme alokovat, a druhým parametrem je velikost jednoho prvku!

Funkce pro práci s pamětí

Protože již víme, že textové řetězce jsou pouhé pole znaků, poskytuje knihovna string.h kromě již zmíněných funkcí pro práci s řetězci také funkce pro práci s pamětí.

memset()

Funkce memset() nastaví všechny bajty na specifickou hodnotu. Jako první parametr přijímá začátek pole a dále hodnotu, kterou má nastavit. Jako poslední parametr přijímá počet bajtů, které má zpracovat. Lze s ní například vynulovat pole.

int* pole = (int*)malloc(sizeof(int) * 10);
memset(pole, 0, sizeof(int) * 10);

Pozn.: Stejného výsledku bychom dosáhli i pomocí funkce calloc(), ale někdy máme jedno pole a vynulujeme ho v každé iteraci nějakého cyklu, což nám memset() umožňuje.

memcpy()

Jak je z názvu patrné, memcpy() kopíruje paměť z jednoho pole do druhého. První parametr je pole, do kterého chceme kopírovat, druhým parametrem je pole, ze kterého kopírujeme a třetí parametr je počet bajtů ke kopírování. Můžeme si tak sami napsat např. funkci pro kopírování řetězců:

char retezec[] = "Ahoj svete!";
char kopie[256];
memcpy(kopie, retezec, sizeof(char) * strlen(retezec));

Pozn.: Funkce strcpy() tak ve skutečnosti nepracuje, ale je implementována ještě efektivněji.

memmove()

Zde název funkce tak úplně neodpovídá tomu, co funkce ve skutečnosti dělá. memmove() paměť ve skutečnosti nepřesouvá, ale kopíruje. Rozdíl je v tom, že memmove() počítá s tím, že se bloky mohou překrývat. Nejlépe asi poslouží ukázka. Pořadí parametrů je stejné jako u memcpy().

char text[256] = "svete!";
printf("Puvodni text: %s\n",text);

memmove(text+5, text, sizeof(char) * strlen(text));
printf("Po presunu: %s\n",text);

memcpy(text, "Ahoj ", sizeof(char) * 5);
printf("Po kopirovani: %s\n",text);
Funkce Memmove v jazyce C

Ukazatele a struktury

Již víme, že ke členům (atributům) struktury přistupujeme pomocí tečky. Máme-li strukturu člověka, ke jménu se dostaneme takto:

CLOVEK pavel;
pavek.vek = 42;

Jiná situace je ovšem pro ukazatele. Nejdříve musíme ukazatel dereferencovat (získat hodnotu z ukazatele) a teprve poté můžeme přistupovat k jednotlivým atributům struktury. Céčko navíc zavádí pro operátory dereferencování (hvězdička) a přístupu (tečka) rozdílné priority. Operátor přístupu má vyšší prioritu a vyhodnotí se dříve (bude probráno v díle o prioritách operátorů). To vede k tomu, že musíme dereferencování ukazatele uzávorkovat, aby bylo vyhodnoceno dříve, jak je ukázáno v dalším příkladu:

CLOVEK* pavel = (CLOVEK*)malloc(sizeof(CLOVEK));
(*pavel).vek = 42;

Rozhodně se nejedná o elegantní zápis. Proto C zavádí další operátor, který tuto konstrukci nahrazuje. Nejdříve dereferencuje ukazatel a následně se přesune na atribut struktury. Tímto operátorem je prostá šipka složená z pomlčky a znaky větší než (->).

CLOVEK* pavel = (CLOVEK*)malloc(sizeof(CLOVEK));
pavel->vek = 42;
strcpy(pavel->jmeno,"Pavel");
char treti_pismeno_ve_jmene = pavel->jmeno[2];

Ukazatel na funkci

Dosud jsme se setkali pouze s ukazatelem na proměnnou v programu. V C můžeme zajít ještě dál. Jak víme, funkce je pouze série příkazů. Tyto příkazy musí být uloženy někde ve zkompilovaném souboru. Také jsme si říkali, že ukazatel pouze ukazuje na nějaké místo v paměti. Může tedy existovat i ukazatel na funkci (lépe řečeno ukazatel na první instrukci funkce). Přirozeně na takový ukazatel nemůžeme aplikovat aritmetiku ukazatelů, ale můžeme funkci pomocí tohoto ukazatele volat. Ukážeme si krátký příklad.

int secti(int a, int b)
{
    return a+b;
}

int main(int argc, char** argv) {

    int(*ukazatel_na_funkci)(int,int);
    ukazatel_na_funkci = secti;

    int vysledek = ukazatel_na_funkci(5,4);
    printf("Vysledek po volani ukazatele na funkci: %d", vysledek);

    return (EXIT_SUCCESS);
}
Použití ukazatel na funkci v C

S ukazatelem na funkci jsme se již setkali. Bylo to v díle o polích - konkrétně při řazení pole. Jenom pro připomenutí napíšu část kódu, která pole seřadila.

int porovnej(const void * a, const void * b)
{
    return (*(int*)a - *(int*)b);
}
// .....
qsort(cisla, 7, sizeof(int), porovnej);

Posledním parametrem je ukazatel na naši funkci, podle které se má pole seřadit. Ukazatel na funkci je jedno z nejpokročilejších témat jazyka C.

Nyní již byly probrány všechny aspekty dynamické alokace v C. Seriál pokračuje další sérií, která se zabývá pokročilými konstrukcemi v jazyce C.


 

Stáhnout

Staženo 3x (126.11 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

  Aktivity (8)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Celkem (2 hlasů) :
4.54.54.54.54.5


 



 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!