IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: C/C++ dynamická alokace paměti

V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Dominik Baričák:22.7.2020 23:44

Zdravím,

potřeboval bych od Vás trochu objasnit problematiku týkající se dynamického (rostoucího) pole a s tím související dynamickou alokaci paměti - malloc, realloc, případně new v C++ apod. Jde o to, že ve všech návodech, videích apod je ten postup prakticky totožný ->

  1. vytvořím si pole na haldě o nějaké velikosti např. int* heapArray = (int*) malloc(3 sizeof(*heapA­rray));
  2. poté, až kapacita tohoto pole nebude dostačovat, tak se použije realloc (data zůstanou, velikost pole se zvětší - většinou např. 2x)
  3. v C++ jelikož realloc není, tak se to buď řeší buď přímo zabudovaným vectorem a nebo vytvořením druhého většího pole a zkopírováním hodnot z prvního do tohoto nově vytvořeného pole.
  4. to vše samozřejmě ideálně zakomponovat do nějaké struktury která bude obsahovat další proměnné, jako kapacita, velikost apod...

Dynamické pole jsem podle kódů z návodů pochopil, ale trochu jsem si s tím hrál a vůbec jsem nepobral pár věcí. Na tyto věci (celkově 2) se ptám přímo v komentářích v první části kódu kódu - příšlo mi to přehlednější, než to vypisovat sem.

první a druhá část je oddělena hvězdičkami

v druhé části jsem se bez nutnosti používat realloc nebo vytvoření druhého pole snažil vytvořit primitivní dynamicky rostoucí pole a zdá se, že vše funguje (žádný problém jsem nezaznamenal).

#include <stdio.h>
#include <stdlib.h>

int main()
{
    // alokace pole na haldě
    int* heapArray = (int*) malloc(sizeof(*heapArray)); // nebo v C++ int* heapArray = new int;
    heapArray[0] = 0; // *(heapArray) = 0;
    heapArray[1] = 1; // *(heapArray + 1) = 1;
    heapArray[2] = 2; // *(heapArray + 2) = 2;

    //v pořádku
    printf("%d ", heapArray[0]);
    printf("%d ", heapArray[1]);
    printf("%d \n", heapArray[2]);

    printf("%p ", &heapArray[0]);
    printf("%p ", &heapArray[1]);
    printf("%p \n", &heapArray[2]);

    //jakou hodnotu má dalších 7 políček a existují vůbec?
    heapArray[10] = 500;

    //pointerová aritmetika funguje
    printf("%d ", *(heapArray+10));
    printf("\n");

    /*pokud funguje kód výše proč bych vůbec při alokaci pole měl udávat počet políček?
    int* heapArray = (int*) malloc(pocet_policek * sizeof(*heapArray)); ? */

    //*******************************************************************************//

    int* heapArray2 = (int*) malloc(sizeof(*heapArray2));
    int velikost_pole = 0;

    printf("zadej pocatecni velikost pole: ");
    scanf("%d", &velikost_pole);

    //počáteční velikost
    for(int x  = 0; x < velikost_pole; x++){
        heapArray2[x] = x;
    }

    for(int x  = 0; x < velikost_pole; x++){
        printf("%d ", heapArray2[x]);
    }

    printf("\n");

    //rozšíření pole
    for(int x  = velikost_pole; x < velikost_pole*2; x++){
        heapArray2[x] = x;
    }

    for(int x  = velikost_pole; x < velikost_pole*2; x++){
        printf("%d ", heapArray2[x]);
    }

    printf("\n");

    //vypsání celého pole
    for(int x  = 0; x < velikost_pole*2; x++){
        printf("%d ", heapArray2[x]);
    }

    free(heapArray2);

    return 0;
}

Jelikož jsem na internetu nic podobného nenašel, tak jsem se rozhodl zeptat se zde vás.
Buď to funguje, protože to prostě fungovat může a není to spatně, a nebo je to totálně celé špatně, ale funguje to (vím, že tohle se u C/C++ stát může) :D.

Děkuji za odpovědi.

Zkusil jsem: Google

Chci docílit: Pochopit proč to funguje

 
Odpovědět
22.7.2020 23:44
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Dominik Baričák
Martin Dráb:23.7.2020 1:00

Dynamická alokace (malloc, calloc, realloc, new...) znamená, že požádáš tzv. správce haldy o blok paměti určité velikosti. Správce ti jej přidělí, pokud takový najde či dokáže vytvořit. Pak ty alokační funkce vrátí adresu různou od NULL.

Správce haldy ale nepracuje ve vzduchoprázdnu – sám si obstarává paměť, jejíž části pak přiděluje, od operačního systému. Operační systém mu ji ale dokáže dát pouze po větších kusech (tzv. stránkách či skupinách stránek, minimálně 4 KB, ale může být i víc), takže správce si v nich musí ještě udržovat nějaká metadata, aby věděl, jaké a jak velké bloky paměti přidělil již někomu dalšímu.

To v zásadě znamená, že pokud si od správce vyžádáš blok paměti o velikosti sizeof(int), tak bys neměl zapisovat mimo něj, protože můžeš (a obvykle se to stane) správci přepsat metadata o tom, jaké části oněch velkých kusů paměti (,které má přímo od jádra operačního systému) již přidělil. Správce z toho většinou není zrovna nadšený, čehož výsledkem je pád programu. Může se ale stát, že tebou přepsaná metadata bude potřebovat až někdy později, takže program nespadne hned.

Tzn. to, co tam děláš, je z té kategorie totálně špatně. Chyby tohoto typu se velmi špatně hledají právě proto, že se projevují o hodně později. Lze ale obětovat (někdy značnou) část výkonu a např. po každé (de)alokaci správce haldy požádat, aby se podíval, zda-li jsou jeho metadata v pořádku.

Jinak, implementace operátoru new v C++ bývá založena právě na malloc. Podobně, std::vector v případě, že se mu data nevejdou, své interní pole přealokuje na větší a původní obsah překopíruje.

Samozřejmě, paměť, kterou si od správce haldy vyžádáš, bys mu měl i vrátit.

Nahoru Odpovědět
23.7.2020 1:00
2 + 2 = 5 for extremely large values of 2
Avatar
JerryM
Člen
Avatar
JerryM:23.7.2020 7:49

máš tam blbost hned nazačátku
int* heapArray = (int*) malloc(sizeof(*he­apArray));
co je to sizeof(*heapArray) ????????? to sou jako 4 byty ???

 
Nahoru Odpovědět
23.7.2020 7:49
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Dominik Baričák
DarkCoder:23.7.2020 7:52

Dost už toho bylo řečeno v předchozím příspěvku, přesto ještě doplním pár informací.

Jazyk C vznikl jako náhrada Assembleru a tak Ti bez jakýchkoli varovných hlášení umožní přístup do paměti, která ovšem nemusí být tvoje. Toto může způsobit pád programu (v tom lepším případě). Vysvětlení viz příspěvek výše. To je důvod proč to funguje, ale je to zkrátka celé úplně špatně. Veškeré ošetření chyb nechává na programátorovi.

Funkce malloc() vrací ukazatel na začátek přiděleného bloku paměti. Je třeba si uvědomit, že toto přidělení paměti není automatické a může selhat. Je tedy bezpodmínečně nutné otestovat vracený ukazatel na to zda přidělení paměti proběhlo v pořádku.

Operátor preprocesoru sizeof je využíván v době překladu, je tedy vhodné jej využívat kdykoli to jde a nespoléhat se na to že znáš velikosti datových typů. Pro stanovení konstant je vhodné používat definici maker, popřípadě proměnné obsahující patřičnou hodnotu. V žádném případě by hodnoty neměly být přímo vpisovány do kódu. Vyhneš se případným nepříjemnostem v hledání vzniklé chyby a program bude lépe čitelný.

Pokud ukazatel na nic neukazuje, je neplatný, je dobrým zvykem jeho hodnotu vždy nastavovat na NULL. V žádném případě nezapisovat data do paměti která není vyhrazena pro tvé potřeby. Změna velikosti dynamicky alokovaného pole se v C provádí za pomocí funkce realloc(). Pro samotné odřádkování je vhodnější použít funkci putchar() namísto printf().

Nakonec praktická ukázka. Zde je správný způsob, jak dynamicky alokovat pole, naplnit ho daty a uvolnit alokovanou paměť.

#include <stdio.h>
#include <stdlib.h>

#define N 3

int main(void) {
        int *arr = NULL;

        arr = (int*)malloc(N * sizeof(int));
        if (!arr) {
                fprintf(stderr, "Chyba alokace pameti.\n");
                exit(1);
        }

        for (int i = 0; i < N; i++) {
                *(arr + i) = i;
                printf("%d ", *(arr + i));
        }
        putchar('\n');

        free((void*)arr);
        arr = NULL;

        return 0;
}
Nahoru Odpovědět
23.7.2020 7:52
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Avatar
Caster
Člen
Avatar
Caster:24.7.2020 12:01

Někdy může být výhodnější alokovat velkou pamět rovnou, než ji postupně zvětšovat. Např. pro načtení celého souboru nejednou do paměti (při jednom čtení, ReadFile(), lze načíst max. 2 501 566 399 bytů). Příklad alokace 5 GB RAM pro práci s daty přímo v ní.

//* Alokujeme pamět pro celý soubor *//
        static const SIZE_T giga = 1024 * 1024 * 1024;
        static const SIZE_T size = 5 * giga;
        ptr = static_cast<BYTE*>(VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE));
        bsrc = ptr;

kde

BYTE* ptr = NULL;
BYTE* bsrc = NULL;

Samozřejmě je ještě nutné otestovat, zda se paměť skutečně podařilo alokovat a před ukončením programu ji pak uvolnit.

VirtualFree(bsrc, 0, MEM_RELEASE);
DWORD errkod = GetLastError();

S daty v RAM lze pak pracovat např. takto:

bodu = *(DWORD *)(bsrc + delka + 4 * x + 4 + k * 4);
 
Nahoru Odpovědět
24.7.2020 12:01
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Caster
Martin Dráb:24.7.2020 13:56

Např. pro načtení celého souboru nejednou do paměti (při jednom čtení, ReadFile(), lze načíst max. 2 501 566 399 bytů). Příklad alokace 5 GB RAM pro práci s daty přímo v ní.

Pokud se jedná o práci s velkým souborem, možná by bylo lepší jej do paměti namapovat (CreateFile + CreateFileMapping + MapViewOfFile). Pak jej není potřeba explicitně číst.

Nahoru Odpovědět
24.7.2020 13:56
2 + 2 = 5 for extremely large values of 2
Avatar
Caster
Člen
Avatar
Odpovídá na Martin Dráb
Caster:24.7.2020 14:33

To máš pravdu, ale při namapování souboru z něj systém stejně musí data nějak po kouscích načítat a určitě je nečte po 2,5 GB. Pokud jde o maximální rychlost načtení a zpracování dat, rychleji než nečíst všechna data do RAM po 2,5 GB a tam je zpracovat, to jiným způsobem nepůjde.

 
Nahoru Odpovědět
24.7.2020 14:33
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Caster
Martin Dráb:24.7.2020 21:48

To máš pravdu, ale při namapování souboru z něj systém stejně musí data nějak po kouscích načítat a určitě je nečte po 2,5 GB. Pokud jde o maximální rychlost načtení a zpracování dat, rychleji než nečíst všechna data do RAM po 2,5 GB a tam je zpracovat, to jiným způsobem nepůjde.

Myslím, že systém (nebo některý ovladač na cestě) bude takto velký požadavek rozdělovat na menší (2-4 MB). Nezdá se mi, že by byl disk schopný sám provést takové čtení jako jednu operaci.

Navíc, pokud pro začátek zpracování stačí jenom menší množství dat, je možné provádět čtení asynchronně a mezitím, co se data donačítávají, zpracovávat ta, co již jsou v paměti. Paměťově mapované soubory v tomhle trošku pomáhají, protože data do paměti načítá systém a pro určité scénáře (jako např. sekvenční čtení) se dokáže chovat chytře. V zásadě ale data načítá až v případě potřeby (+ ukládání do cache), takže v případě, že nakonec zjistíš, že třeba všechna data nepotřebuješ, se ta nepotřebná do paměti vůbec nemusí podívat.

Nahoru Odpovědět
24.7.2020 21:48
2 + 2 = 5 for extremely large values of 2
Avatar
Caster
Člen
Avatar
Odpovídá na Martin Dráb
Caster:25.7.2020 12:09

Čtení po 2,5 GB není žádný problém, zvlášť z rychlého SSD disku. Načtení 5G je otázka cca dvou sekund.

Příklad jak na to:

const DWORD cti_blok = 2501566399;
delka = FileSize(fname); // 64 bitů

//* Zkusíme otevřít soubor pro čtení *//
HANDLE handle = CreateFile(fname, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
// if (handle == INVALID_HANDLE_VALUE)
// report_error("Unable to open input file!\n");

a vlastní čtení:

//* Načteme soubor *//
        DWORD bytes_read;
        cti_znaku = cti_blok;
        do {
                BOOL e = ReadFile(handle, ptr, cti_znaku, &bytes_read, nullptr);
                nacteno += bytes_read;
                ptr += bytes_read;
                if (delka - nacteno < cti_blok) {
                        cti_znaku = delka - nacteno;
                }
        } while (nacteno < delka);
        CloseHandle(handle);
 
Nahoru Odpovědět
25.7.2020 12:09
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Caster
Martin Dráb:25.7.2020 12:29

Čtení po 2,5 GB není žádný problém, zvlášť z rychlého SSD disku. Načtení 5G je otázka cca dvou sekund.

Pokud takový disk máš, tak samozřejmě ano :-).

Nahoru Odpovědět
25.7.2020 12:29
2 + 2 = 5 for extremely large values of 2
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.

Zobrazeno 10 zpráv z 10.