Přidat otázku mezi oblíbenéZasílat nové odpovědi e-mailem C/C++ dynamická alokace paměti

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(*heapArray));
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.

Předmět Autor Datum
Alokuješ to nějak podivně. Má to být pole intů, ale bereš tam velikost ukazatele. No a že ti to umož…
Wikan 22.07.2020 22:22
Wikan
Myslel jsem si, že to bude právě to co popisuješ. Jen mě trochu zarazilo, že to funguje až tak věroh…
arraymaster 22.07.2020 22:39
arraymaster
Je to totalne cele spatne a funguje to ciste souhrou okolnosti. zacnu nejdriv od konce - zadny prob…
gilhad 23.07.2020 03:11
gilhad
Jo a pokud se ti to zda slozite, tak mas pravdu, (a to jsem to jeste velice zjednodusil a spoustu ve…
gilhad 23.07.2020 03:35
gilhad
Děkuji všem za velmi poučné rady, které pomohly! :) nechtěl jsem se vrhat do něčeho dalšího, dokud a… poslední
arraymaster 23.07.2020 22:16
arraymaster

Alokuješ to nějak podivně. Má to být pole intů, ale bereš tam velikost ukazatele.
No a že ti to umožní sáhnout i mimo to, co jsi alokoval? Ano, umožní. Ale už saháš někam, kam bys sahat neměl a to může skončit i pádem aplikace.
V jiných jazycích je na to často kontrola, ale v C/C++ ne.

Myslel jsem si, že to bude právě to co popisuješ. Jen mě trochu zarazilo, že to funguje až tak věrohodně :D dokonce i aritmetika ukazatelů.

int* heapArray = (int*) malloc(sizeof(*heapArray)) a int* heapArray = (int*) malloc(sizeof(int)) by teda podlě mě mělo být ekvivalentní. zápisem *heapArray ziskám hodnotu na kterou tento ukazatel ukazuje -> tudíž int.

Opravte mě, pokud píšu kravinu.

Je to totalne cele spatne a funguje to ciste souhrou okolnosti.

zacnu nejdriv od konce - zadny problem jsi sice nezaznamenal, ale neznamena, ze by tam zadny nebyl a ze by ti to klidne mohlo i spadnout, nebo delat dost nepredpokladane veci. Ale ten programek je tak maly a jednoduchy a nedela skoro nic, takze ti to nejspis nahodou z tohoto duvodu proslo.

Jako kdyz vlezes do prazdneho mistenkoveho vlaku s jednou mistenkou, sednes si na to misto, na druhe si das tasku, na treti noviny a na ctvrte hamburgr. Pak to zase rychle sbalis a nez prijde pruvodci, nebo jini cestujici, tak vypadnes z vlaku a jdes domu s pocitem, ze vse funguje a zadny problem jsi nezaznamenal.

Pricemz o spouste veci (zatim) nevis (ze sedadlo naproti ma od dalsi stanice rezervovane nekdo jiny) a mnohe treba ani vedet nepotrebujes (jak funguje motor lokomotivy, napriklad).

Pro zacatek odhledneme od toho, ze kompilator ma pomerne velkou volnost, jak tvuj program optimalizovat uz pri prekladu a ze vysledny kod vlastne vubec nemusi vypadat jako to, co jsi napsal, jen by se tak mel v dusledku pro vnejsiho pozorovatele jevit (pokud jsi to vsechno napsal spravne - pokud ne, tak ma v mnoha pripadech kompilator moznost udelat absolutne cokoli, i kdyz vetsinou jen vypise nejake varovani, nebo si usetri praci a tvoje chyby neresi a jak to vyjde, tak to vyjde).
A operacni system se stara nejen o tvuj program ale i o spoustu dalsich co bezi "naraz" s nim (at uz to znamena co chce) a o pamete (kdyz potrebuje RAM, tak treba tvoje promenne ulozi na disk do swapu, program rovnou prepise (protoze si ho muze kdykoli zase z disku nacist), RAM pouzije na cokoli jineho a pak zase vrati veci zpatky a tvuj program o tom nema ani tuseni.
A protoze toho ten system ma na praci hodne, tak se vetsinou nestara o jeden kazdy byte, ktery si na nem vyzadas, ale vyhradi pri prvnim pozadavku te pameti nejaky uceleny blok, pri dalsim pozadavku na pamet proste vrati ukazatel na dalsi misto v bloku, co ti jeste nepridelil a teprve az ten blok vycerpa a ty chces vic, tak ti prideli dalsi blok a zase z nej bere ...

Takze jako v tom rychliku, kdyz se o kousek seknes, muze se stat, ze tam bude zrovna volno a projde ti to, protoze ve vlaku je cely vagon. A nebo tam zkusis dat tech veci vic a najednou jsi na konci vagonu a dalsim krokem spadnes na koleje. Nebo jen das neco do vagonu, co se bude po ceste preprahat a pojede jinam a najednou pulku veci nemas. Ale protoze sis nekoupil mistenky (nealokoval patricne pamet), tak je to tvuj problem. Nebo te chytne pruvodci a z vlaku vyhodi.

Ale nez se tvuj program spusti, tak se stane spousta veci, napriklad se jeho kod ulozi nekam do pameti, nekde v pameti se alokokuje misto pro vsechny texty, ktere tam mas (treba %d z tech printu), misto pro zasobnik, misto pro staticke promenne, misto pro haldu a spusta dalsiho.
Pak se tvuj program spusti a magie pokracuje - knihovni funkce ti napriklad zaridi pristup ke standardnimu vstupu a vystupu (takze ten print ma kam psat), nastavi se promenne obsahujici zacatek a konec pouzite haldy (a taky treba seznam der v ni) a tak dal a tak dal.
Pak se po ruznych peripetiich spusti tvoje funkce main a (krome jinych veci) ti na zasobniku vytvori misto pro promennou heapArray, co by mela obsahovat ukazatel na int (teda az tam nejaky vrazis, pokud vubec).

Nasledne pozadas o ukazatel na jedno misto pro int (malloc) a dostanes mistenku (jeho navratova hodnota), ktera ti dava pravo si sednout a rika kam. (BTW v textu pises neco jako 3*sizeof(int), v kodu uz jen sizeof(int), takze nasledne indexy 1 a 2 uz jsou taky mimo hranice alokovane pameti)
Nasledne si na to misto sednes (heapArray[0] = 0;) a zatim je vse v poradku (akorat teda ten vlak ma z praktickych duvodu cely vagon, o kterem se vi, ze je v nem jedno misto obsazeno a zbytek volny, jde tedy na nej prodat mistenky = alokovat ho pro cokoli si rekne)
Pak na dalsi misto das tasku (heapArray[1] = 1) - ale ackoli tam nahodou zrovna misto je (protoze system ti tam dal z praktickych duvodu celou stranku) tak uz ti nepatri (v malloc sis zarezervoval jen 1 int) ackoli sis doma rikal, ze ji ty mistenky koupis tri (v tom uvodnim textu).
Takze pokud by kdokoli v tu chvili pozadal malloc o nejake misto, tak dostane prideleno ( = mistenku na, ) to misto hned za tebou, kde ted lezi taska. Pri trose smuly by to mohl byt i ta funkce print o par radku niz, co vypisuje heapArray[0] a mohla by si tam popravu dat cokoli by ji napadlo ( tim zrusit tvoji tasku, teda tu hodnotu 1 prepsat cimkoli) a pokud by pak to misto vratila, tak uz by tam tvoje taska proste nelezela. A pak by treba ten dalsi print vypsal misto tasky (heapArray[1] = 1) batoh s lyzema (nejake naprosto jine cislo), co tam byl naposled.

A ano, samozrejme, pokud tvoje sedadlo ma cislo 0, tak dalsi sedadlo ma cislo 1 a dalsi cislo 2 atd ... Takze si klidne muzes pocitat sedadla jak chces, i kdyz na ne mistenky nemas, jen ti nikdo nezaruci, ze bez tech mistenek ta sedadla budou volna a co tam das tam i zustane, nebo ze kdyz tam neco das, tak tim neznicis neco jinemu radnemu pasazerovi a nebude to mit necekane dusledky). Nebo ze kdyz tam neco zkusis dat a uz je to v dalsim vagonu a ten neni pripojeny, tak ze nespadnes na koleje (system nevyhodi chybu pristupu a nasilne neukonci tvuj program).

Takze zkus si udelat tohle:

- alokujes si jedno pole a vypises si jeho adresy . printf("%p ", &heapArray[0]); atd ...
- alokujes druhe pole a opet si vypises jeho adresy printf("%p ", &heapArray2[0]);
- do prvniho pole zapises dost veci, aby ti to vyslo az prez adresy toho druheho pole (treba cisla od 0 do 2*velikost_pole)
- do druheho pole zapises dost jinych veci (treba cisla od 1000 do 1000+velikost_pole)
- ted si zkus vypsat to prvni pole az do 2*velikost_pole a najednou uvidis, ze to od nejakeho cisla je uplne blbe
- tak to "oprav" zapis tam ta puvodni cisla jeste jednou
- ted zkus vypsat to druhe pole a "hrozne se div" proc ti nezacina 1000, ale necim uplne jinym (radove tak velikost_pole mozna plus nejake drobne na rezii)

Cimz mame odpoved na otazku, proc pri alokaci musis udavat pocet policek - protoze ti je nasledne alokovano jen tolik, kolik jsi zadal a nasledujici mohou byt alokovany pro nekoho jineho (treba heapArray2)
A proc to vubec "fungovalo" - protoze dopravce misto jedne sedacky pripojil cely vagon.
A proc je to uplne spatne - protoze kdyz pises nekam, kde sis to nealokoval, tak tam muze byt klidne neco jineho, co zmrsis (s naproste nahodnymi nasledky) a taky proto, ze kdyz pak odtamtud ctes, tak tam vubec nemusi byt to, co jsi tam zapsal (ale to, co tam zapsal ten druhy, co si alokoval kus pameti pro sebe = koupil mistenky - a pak si tam zapsal co chtel on)

A proc je to tak uplne spatne - protoze jakmile si takhle zacnes prepisovat pamet s nejakou jinou casti kodu (at tvoji, ci knihovni) a pak chces ty hodnoty pouzit, tak to vubec nemusi fungovat a vubec nemusis tusit proc - a nekdy ano a nekdy ne.
A proc je to jeste hur - proteze vubec netusis, co si kde ktera knihovni funkce uklada do pameti, kterou si pro to alokovala a co se stane, kdyz se ji najednou pod rukama ty hodnoty zmeni, protoze ty si pises kam chces co chces, u kdyz nemas mistenky.
(cimz se klidne mohlo stat, ze jsi prepsal treba dnesni datum, pridelenou IP adresu a cestu k aktualnimu adresari nejakyma hovadinama - no dnes se ti az tak uzasne veci spis nepovedou a pruvodci=OS te proste vykopne z vlaku=ukonci ti program uprostred s nejakou divnou chybou. Ale klidne muzes prepsat neco svojeho, nebo neco lokalnejsiho a mene hlidaneho ...)

Jo a pokud se ti to zda slozite, tak mas pravdu, (a to jsem to jeste velice zjednodusil a spoustu veci preskocil a nezminil). Programovani obecne je slozite a v C/C++ zvlast, ono to ma furt o dost bliz k assembleru ci strojovemu kodu, nez mnohe jine jazyky. A tak to taky zustane, protoze cenou za nutnost peclive prace je moznost dosahnout vysokeho vykonu. Takze treba spousta jinych jazyku (co te vic vedou za rucicku a vic nenechaji udelat chybu) pouziva knihovny psane v C, aby jim slozite konstrukce fungovaly rychle.

Je spousta jazyku, ktere by ti podobne konstrukce naprosto normalne umoznily a dokonce i vetsi odvazy, ale zase za to casto platis treba vykonem, kdy ten jazyk i za behu kontroluje, co tropis a nenapadne tvym jmenem alokuje novou pamet, pokud se ji pokusis pouzit a kdyz to nejde jen protahnout, protoze uz neco prekazi, tak to klidne zkopiruje a prehaze ukazatele, abys nic nemusel resit a nevyhodili te z vlaku.

A jsou jazyky, co treba neplatis az tak vykonem, ale napriklad tim, ze mas silne omezeno co a jak muzes delat, aby se ten kod vubec bral jako program a nejak dokopal ke zpracovani (napriklad v jazyce KAREL napr. http://www.holubec.cz/karkniha/kar98kni.htm ti zadne prekroceni hranic alokovane pameti nehrozi, protoze to proste nemas jak zapsat. Ale mas jen par prikazu a silne omezeno, co vubec muzes delat.)

A treba assembler ti da jeste mnohem vetsi moznosti ve smyslu, ze ti nezakaze psat cokoli kamkoli, jakkoli a muzes docilit neuveritelne rychleho behu programu - ale zase neni vubec jednoduche v nem neco vetsiho napsat tak, aby to vubec fungovalo. Klidne te necha nejenom se strelit do nohy, ale klidne si ji celou pomalu pizlat rezavou lzici - pokud to tak chces ... nebo teda spis co napises a vyhovuje syntaxi, to nejspis udela a ze si tim tu nohu pizlas lzici mu je zcela ukradeno - ma sve prikazy a bez premysleni je plni - zadna typova kontrola, zadne pozadavky na vraceni hodnot z funkci, nebo na prepisovani si navratovych adres na zasobniku - vubec netusi, ze by to mohlo byt blbe a proste prikaz provede. Stovky milionu prikazu za sekundu, ci vic, klidne na kazdem z jader procesoru.

Naopak SQL ti abstrahuje praci s daty na "databaze", "tabulky" a "sloupce" a jednim prikazem klidne zpracuje data z desitek souboru, pomoci vyvazenych binarnich stromu, s automatickymi vazbami a volanim predem danych uloh pri zmene, vkladani, ci mazani a takovy prikaz klidne muze bezet hodiny a zpracovat miliony udaju - neco, co bys v C/C++ psal na tisice nebo miliony radku. Opravdu.

Děkuji všem za velmi poučné rady, které pomohly! :) nechtěl jsem se vrhat do něčeho dalšího, dokud aspoň částečně nepochopím tohle. Teď se můžu pustit do dalších pokusů.

gilhad: zvlášt tobě děkuju za vyčerpávájící odpovědi :-) :-D. Ten příklad s lokomotivou, vagónky apod byl skvělej.

Zpět do poradny Odpovědět na původní otázku Nahoru