Stránka 1 z 2

Správná alokace paměti

Napsal: 02 kvě 2018, 20:06
od michal123
Dobrý večer, od proměnné typu int a=5; jsem začal používat char b[10] a používal funkce strcpy()..., potom char *c=new char[12];, nyní vyuzívám velkou část SRAM a dochází k přepisování částí proměných, snad únikum paměti a přeskakování kódu programu. Začal jsem hledat a zjišťovat jestli bych neměl používat malloc(), kde vlastně a jak.
Prosím o radu, jak a kdy alokovat paměť aby vše fungovalo jak má.

Re: Správná alokace paměti

Napsal: 03 kvě 2018, 06:25
od gilhad
Neni to primo odpoved na tvou otazku, ale me se zacala pamet prepisovat zasobnikem, kdyz byla moc plna a dost mi pomohlo pouziti PROGMEM pro vsechny texty (a jine konstanty) v programu. Ono stejne jsou v te FLASH nahrane, aby se pri startu mohly prekopirovat do RAM a pouzivat "normalne", tak proc je nenechat v te FLASH a brat si je to co zrovna potrebuju na tak dlouho, jak to potrebuju, ze?

Usetril jsem tak prez pulku RAM a to uz se pozna ...

Na druhou stranu samozrejme je potreba aby clovek tu RAM neleakoval, takze dealokovat vse, co jsem alokoval a uz nepotrebuju a tak je jasny zaklad.

Dalsi problem pak muze delat fragmentace haldy v RAM, kdyz na preskacku alokujes a dealokujes ruzne velke kusy, tak ti tam muzou vzniknout male kousky, kde sice nic neni, ale ani se tam nic rozumne nevejde ... a kdyz je jich dost, tak to muze znamenat hodne nepouzitelne pameti (cili RAM dojde podstatne driv, nez mas alokovano teoreticke maximum, protoze tam jsou ty diry)

Predstav si , ze alokuju 64 bloku po 16 bytech (dohromady 1KB), pak uvolnim kazdy lichy (takze pouzivam 512B) a pak alokuju 32 bloku po 32 bytech (coz je dohromady 1KB) a uvolnim kazdy lichy (takze z nich zase pouzivam 512B) - kolik pameti jsem zabral? 512+512=1KB? A kdyz zkusim alokovat treba 64B, tak zadny problem ve 2KB RAM? - ve skutecnosti sice drzis 1KB, ale tech 64B se pokusi alokovat NAD hranici 2KB (protoze tam konci halda ted a uvnitr proste neni nikde dost souvisleho mista) a jsi zcela mimo pamet (o zasobniku ani nemluve) - jasne ukazkovy skolni priklad, ale podobne veci se rady dejou nepozorovane a postupne i v realnem svete, jen to vetsinou eskaluje trosku pomaleji ...

Re: Správná alokace paměti

Napsal: 03 kvě 2018, 15:49
od michal123
Ano, snažil jsem se ukládat do progmem (o tom až později), jsou správné tyto příklady alokace? (jde to dělat lépe?, třeba s nějakým makrem?)
char *ret = (char*)malloc(8);
int a = (byte*)malloc(2);

char *ret = (char*)malloc(8);
*ret=new char[8]

delete[] ret;
ret=NULL;

každý příklad ukončený free() a if pro možnou chybu od malloc() kterou jen vypíšu abych věděl že je něco špatně. Můžete případně poslat správné příklady?

Je nějaký způsob jak zjistit fragmentaci paměti?
nebylo by jediné správné používat typy jako int8_t místo int a char, fungovalo by například strcpy() na typ uint8_t? případně jsou stejné cstringfunkce i pro znaky 0-255 uint8_t aby se paměť max využila?

Re: Správná alokace paměti

Napsal: 03 kvě 2018, 17:56
od gilhad
uint8_t, int8_t a uint16_t pouzivam zcela bezne a funguji tak, jak maji - pokud potrebuju neco do 8 bitu, nema cenu pouzivat int, ktery vezme nejen 2x vic mista v RAM, ale znamena i podstatne vic kodu v programu (AVR umi dost 8-bit operaci jednou instrukci, zatimco 16-bit uz chce 3 a vic instrukci, o 32-bit ani nemluve).

dynamickou alokaci vlastne ani moc nepouzivam, s vyjimkou promennych, co pretrvaji po celou dobu behu, takze je alokuju v setupu a neuvolnuju - protoze az do restartu jsou porad platne a pouzivane.

dos veci alokuju na stacku jako lokalni promenne, ktere konci s koncem bloku (tedy napriklad funkce, nebo i jen pouheho if/while/...) a promenne deklaruju az tam, kde je potrebuju (typicky v nejake vetvi if-u uprostred funkce a po skonceni if-u se zase uvolni, protoze skonci blok). Prekladac to zvlada bez problemu. (mam asi jiny use-case, takze moc skutecne dynamickych promennych nepotrebuju)

jestli se nepletu, tak malloc je v paru s free pro alokovani bloku bytu, zatimco delete je v paru s new pro alokovani objektu, poli a typu a neni dobre je volne michat.

https://en.wikipedia.org/wiki/New_and_delete_(C%2B%2B)
http://www.cplusplus.com/reference/new/ ... 0delete[]/

https://stackoverflow.com/questions/184 ... loc-vs-new

ale zase pozor, obavam se ze zrovna exceptions na AVR nejsou zrovna dobry napad, pokud vubec jsou podporovane (a tak nevim, jak new resi nedostatek pameti)

https://stackoverflow.com/questions/346 ... exceptions

Re: Správná alokace paměti

Napsal: 05 kvě 2018, 16:23
od michal123
S datovým typem jsem to myslel tak, že kadyž bych do paměti přidělil int s nulovou hodnotou, zabere 2B nebo 1B a když bude víc tak udělá díru v paměti a přesunese na kapacitu 2B?

Nějakou dynamickou alokaci (nejsem si jist co je danymická, statická, globální a snad nějaká jiná) používám. použil jsem jak statickou paměť, nejspíš bez řádné alokace i danymickou (pres new a delete) ale program selhává při obou možnostech. raději bych preferoval malloc() jenž vrací NULL když nemůže alokovat pro nedostatek paměti, s čím už jde nějak pracovat.

Kolem míchání new a malloc si vůbec nejsem jistý jestli se tomu dá věřit, zatím si myslím že každý kompilátor to řeší různě a myslím že takové obecné informace nestačí k určení jak se musí na jaký kompilátor. Máte nějaké zkušenosti s arduinem případně funkční kódy které by nějakou teorii potvrdili?
Jak zjistím co má arduino za kompilátor případně nějaký manuál k němu?

Jaký rozdíl je mezi alokováním v setupu a mimo setup/loop ?

Re: Správná alokace paměti

Napsal: 05 kvě 2018, 19:33
od gilhad
Pokud alokujes int, tak se alokuje na plnou delku sveho datoveho typu (an AVR tusim 2B), bez ohledu na hodnotu, jakou do nej das. Tedy int bude zabirat stejne misto v pameti, at uz v nem budes mit celou dobu pouze 0 (a nikdy ji nezmenis), nebo do nej budes davat hodnoty v celem jeho rozsahu.

Dalsi vec je, ze prekladac muze umistovat delsi datove typy na "zarovnane adresy", ale to uz je dost pokrocila zalezitost.

lokalni promenna - plati jen v nejakem bloku (treba funkci), alokuje se az vypocet do toho bloku dojde a az ho opusti, tak se dealokuje. Proto taky nikdy nevracej z funkce ukazatele na jeji lokalni promenne (protoze mimo tu funkci uz "neexistuji" a co tam v te pameti vlastne je neni nijak zaruceno (kdokoli to mohl prepsat cimkoli a mozna to i aktivne pouziva) - alokuji se na zasobniku. V Cecku jsou i mensi bloky nez funkce a na jejich lokalni promenne se to vztahuje taky.

Dynamicka alokace - zjednodusene malloc nebo new - alokuje misto pro data za behu programu (a zase se to misto da uvolnit - free nebo delete).
- pokud udelas cyklus "for(;;){malloc(2);};", tak ti postupne naalokuje veskerou volnou pamet.
Pouziva se to zejmena kdyz
- potrebujes nejakou pamet, ale kolik ji bude zjistis az za behu programu
- potrebujes nejakou pamet na nejakou chvili (delsi nez volani te dane funkce) a pak ji zase chces uvolnit az ji nebudes potrebovat (typicky ti funkce nekde neco nacte a ma ti to nejak vratit - kdyby to mela v lokalni promenne, tak se skoncenim funkce


Alokace na zasobniku - to jsou "normalni lokalni promenne ve funkci" - alokuji se na zasobniku kdyz se funkce spusti a dealokuji se kdyz funkce skonci (a tim se vrati zasobnik) - pri dalsim volani funkce se alokuji znovu, takze si "nepamatuji" jakou mely hodnotu pri minulem volani funkce
-

Kód: Vybrat vše

void pokus(){ 
  int x;
  return;
  }
...
for(;;){ pokus(); };
ti pri kazdem zavolani funkce "pokus" ulozi na zasobnik adresu, kam se ma po skonceni volani funkce vratit, pak na zasobniku alokuje misto pro promennou "x" (tim posune zasobnik o dalsi 2B); pak kdyz funkce skonci, tak uvolni "x", vyzvedne ze zasobniku navratovou adresu, skoci na ni a pokracuje dalsim prikazem. Tady je v nekonecnem cyklu, takze ten zasobnik porad dokola naroste o 4B (adresa a "x") a zase se o ne zmensi - pamet nikdy nedojde.

Rekurze - kdyz funkce vola sebe samu (primo, nebo prez prostrednika, vetsinou podle nejake podminky a je to obcas velmi elegantni reseni a obcas velky problem, kdyz se to udela spatne)

Kód: Vybrat vše

void pokus(){ 
  int x;
  pokus();
  return;
  }
...
pokus();
tady je neomezena (coz je spatne), ale zase pri kazdem zavolani funkce "pokus" ulozi na zasobnik adresu, kam se ma po skonceni volani funkce vratit, pak na zasobniku alokuje misto pro promennou "x" (tim posune zasobnik o dalsi 2B) pak ZNOVA zavola funkci pokus, tedy znova da na zasobnik navratovou adresu a znova si tam vyhradi misto po "x" (tedy dalsi 2+2B), dokud nevycerpa veskerou pamet.

globalni alokace - to jsou promenne deklarovane mimo jakoukoli funkci - prekladac jim priradi misto v pameti uz pri prekladu a muze k nim pristupovat kazda funkce - zabiraji RAM celou dobu (jeste driv, nez se program spusti)

Kód: Vybrat vše

int LED=13;
void setup() {
....
staticka alokace - i uvnitr funkce muzes deklarovat "globalni promennou", ktera se alokuje pred zacatkem programu a co do ni funkce pri jednom volani zapise, to tam pri jinem volani najde. Je tedy alokovana i kdyz funkce sama (zrovna) zavolana neni. Od globalnich promennych se lisi jen tim, ze je ostatni funkce "nevidi" a nemuzou je tudiz pouzivat. Casto se takto deklaruji ruzne buffery, nebo jine podobne veci a funkce muze jine funkci predat jejich adresu, takze je jine funkce muzou pouzit prez ten ukazatel. poznaji se podle klicoveho slova "static"

Kód: Vybrat vše

void pokus(){
  static int x;
  return;
  };
ukazatel - je promenna obsahujici adresu cehosi (casto dat) (obdobne jako char je promenna obsahujici znak a int je promenna obsahujici cislo) - na AVR vetsinou zabira 2B.
takze

Kód: Vybrat vše

int *a = (byte*)malloc(2);
deklaruje promennou jmenem "a", ktera zabira 2B a je typu ukazatel (na hodnotu typu int) a ten malloc(2) dynamicky alokuje DALSI 2B kdesi v pameti a do te promenne "a" ulozi jejich adresu. (ukazatel si muzes predstavit i jako "index bytu v pameti" kdy na celou pamet pohlizis jako na pole)
Ukazatele se pouzivaji pro:
- pristup k dynamicky alokovane pameti
- vytvareni seznamu libovolne delky (omezeno pameti, ale muzes mit ukazatel na strukturu, co ma v sobe ukazatel na dalsi strukturu a takhle vytvoris seznam, kdy od jedne jdes k druhe, treti ... az dokud ten ukazatel neni NULL)
- pro predavani adresy dynamicke pameti alokovane ve funkci mimo funkci ven
- pro predavani jakekoli adresy kamkoli je treba (muzes mit klidne nekolik globalnich promennych stejneho typu a udelas si ukazatel na tento typ a pak ho predas funkci, ktera neco udela s mistem, kam ten ukazatel ukazuje - napriklad ho vypise na Serial)
- pro predavani adresy pole do funkce (ktera pak do toho pole muze treba zapisovat data)
- pro predavani adresy funkce (ano i to jde) - a pak muzes mit funkci, ktera aplikuje "neco" na spoustu dat a co presne ma aplikovat ji predas jako ukazatel na funkci, co to aplikuje (hledani maxima, minima, scitani, prumer, preneseni prez nejake obskurni medium ...)
- spousty jinych zajimavych veci (a ano, da se tim velice zajimavym zpusobem strelit do nohy)

Rozdil mezi alokaci v setupu a loop a jine funkci neni zadny, jenom ze setup se dela jen jednou a loop vetsinou opakovane a funkce podle toho kdy ji zavolas. Ja tim chtel rict jen to, ze si tu pamet alokuju jen jednou (v setupu), a pak uz ji porad dokola pouzivam (v loop a funkcich volanych z loop) a neuvolnuju ji, protoze ji pouzivam porad dokud to arduino bezi (a tudiz mi bezi opakovane loop) a az se vypne, tak uz ji zase neni potreba uvolnovat, protoze pri novem startu zadna pamet alokovana neni (dokud to nedobehne do setupu a neudelam novou alokaci)

Re: Správná alokace paměti

Napsal: 05 kvě 2018, 20:26
od michal123
Moc děkuji za úžasné vysvětlení. Pro ověření zkusím naspat co z toho pro moji aplikaci plyne.

globální proměnné stačí alokovat typem int8_t x; protože budou mít místo v paměti pořád stejné a to hned z počátku.
lokální bych tak mohl alokovat také ale to bych se nedověděl případné selhání, takže je na začátku funkce alokuji přes malloc a na konci zase uvolním čímž z nich vlastně udělám dynamické.
dynamické nemohu alokovat přes new protože bych zase nedostal případnou chybu takže i pro ně použiji pouze malloc (budou od "lokálních" k nerozeznání).

pro malloc bude než makro výhodná funkce jenž mi případně vypíše a zaznamená chybu.

když chci z funkce návratovou hodnotu tak to musí jít přes globální nebo dynamickou promennou a nebo do pole na které předám ukazatel parametrem při volání.

lze ve funkci zjistit (když ji v parametru přijde adresa), jakou velikost má naalokovanou?
(předpokládám, že ne protože NULL by zabíral v paměti žbytečné místo a navíc by možné hodnoty přisly o jeden stav)

Re: Správná alokace paměti

Napsal: 06 kvě 2018, 00:56
od gilhad
Pokud predas jen ukazatel, tak nelze zjistit, na kolik pameti ukazuje (a nekdy to ani nedava smysl, muze to byt ukazatel doprostred pole a ukazovat na konkretni prvek, nebo na cely zbytek toho pole, nebo treba jen na znaky do prvni nuly - konce retezce).

Ale samozrejme muzes do funkce predat ukazatel a cislo, kolik prvku za nim se ma zpracovat (pokud to nejak neplyne z kontextu - napriklad vis, ze funkce zpracovava vzdy jen jeden prvek, nebo vis, ze funkce pracuje s retezci a dostava pouze smysluplne parametry, takze ta delka je od one adresy po prvni znak s hodnotou \0 ).

A samozrejme kdyz predas funkci ukazatel, tak ta funkce ani nevi, zda ukazuje do neceho alokovaneho pomoci malloc, nebo do nejake globalni promenne, nebo dokonce do lokalni promenne alokovane na zasobniku volajici funkce. A funkce muze jen doufat, ze ukazatel opravdu ukazuje na typ, ktery ona ma ve sve hlavicce (ale neni problem ji zavolat pretypovany ukazatel na cosi zcela jineho, pripadne ukazatel ukazujici do nealokovane pameti - ackoli je to obecne prasarna, tak udelat to lze a lze to i uspesne pouzivat, pokud clovek sakra dobre vi co dela, nebo ma holt dost pameti a stesti)

lokalni promenne (pokud maji opravdu byt jen lokalni) je lepsi alokovat na zasobniku (tedy proste deklarovat ve funkci), protoze se jednak automaticky dealokuji ukoncenim funkce a zadruhe tim muzes usetrit i dost pameti - jak jsem rekl, "int *a=malloc(2)" ti zabere 4B RAM, zatimco "int a" pouhe 2B.

Navic kod pracujici s "int a" bude kratsi, protoze nemusi dereferencovat ukazatel, aby dostal integer a chytry prekladac si to muze upravit dokonce tak, ze tu promennou vubec nebude alokovat, pokud ma dost volnych registru, kde by ji po dobu platnosti mohl drzet (cili usetri i ty 2B RAM). Nemluve o tom, ze malloc a free znamenaji prece jen radu instrukci (a take pouzivaji zasobnik a maji sve lokalni promenne, i kdyz po sobe pak uklidi)

takze pouzivat "int *a=malloc(2)" ma smysl jen tam, kde opravdu nutne potrebujes ukazatel do pameti.

A mimochodem, pokud ti vadi, ze v situaci S se pri "int a;" nedozvis, ze ti dosla pamet, tak pri "int *a=malloc(2)" se sice dozvis, ze malloc uz nemuze alokovat, ale pokud jsi pri tom byl uz v situaci S, tak ses hlavne nedozvedel, ze alokaci UKAZATELE uz ti dosla pamet jeste pred volanim malloc, ktere to zhorsi o sve lokalni promenne a pouziti zasobniku pri jeho volani.

Takze je neprazdna skupina situaci, kdy ti to s "int a" jeste s odrenyma usima projde, ale s "int *a=malloc(2)" uz tvrde narazis a nebudes vedet proc. (napriklad uz jen zavolani malloc te stoji 2B pro navratovou adresu, tere muzou neco prepsat jeste nez malloc sam naalokuje sve lokalni promenne (cimz zase neco muze prepsat) a pomoci vypoctu zjisti, ze neni volna pamet)

---

Ale vazne by ses asi mel dovzdelat (neber to ve zlem, ja se porad dovzdelavam ve spouste podobnych veci i po te, co se desitky let zivim programovanim) co se tyce prace s pameti a ukazatelama a haldou a zasobnikem a tak nekde, kde je to uceleneji popsano a ne jen takhle mezi reci na foru. (Priznej si, ze uz se dostavas dost mimo oblast "a ted si behem 10 minut ukazeme jak snadno a rychle blikat LEDkou, detaily nejsou dulezite, hlavne, ze to nejak blika" kde Arduino bezne zacina a pro takovych 80% uzivatelu i konci).

Jakmile budes chtit z Adruina vymacknout maximum, tak se bez urcite znalosti toho, jak funguje Ccko, jak to vypada ve strojaku, jak je organizovana pamet a tak proste neobejdes.

---

Ja zacinal s Turbo Pascalem a Turbo Assemblerem a hodne toho bylo krasne popsano v knizce "Bity do bytu" od Ladislava Zajicka. Coz jsou knihy ktere mam v knihovne dodnes (a nedam).

Ted jsem zkusil dohledat neco pro zacatek na internetu a uz to nejak nedokazu, co mam zabookmarkovane jsou problemy na zcela jine urovni a veci pro zacinajici nevhodne a prilis dlouhe ( tohle je v Arduino UNO uvnitr http://ww1.microchip.com/downloads/en/D ... asheet.pdf )

Arduino se programuje v C++, takze zkus najit neco o nem, co by bylo pro tebe prijatelne.

Tady ja nejsem dobry radce, ja osobne bych si rad precetl treba tohle (necelych 30$ bez postovneho): http://www.stroustrup.com/Tour.html (od tvurce C++): The ``tour'' is a quick (about 180 pages) tutorial overview of all of standard C++ (language and standard library) at a moderately high level for people who already know C++ or at least are experienced programmers.)
V Cecku jsem napsal sotva desitky tisic radek (no mozna nizke stovky) a rozhodne bych netvrdil, ze ho dokonale ovladam. (A jsou jazyky, ve kterych jsem toho napsal podstatne vic a taky v nich stale nachazim nove veci, ktere mi zatim unikaly.) Ale ono pro zacatek a spoustu ucelu staci aspon trochu znat aspon nejakoe zaklady a uz se s tim da delat docela dost.

Ale na prvni pohled by https://www.sallyx.org/sally/c/c05.php mohlo byt dost kratke a citelne a pritom dost podrobne a relevantni (ne jen ta jedna stranka, vsechno to okolo, ono toho fakt neni malo).
(A atmega328 se svymi (na dnesni doby) celkem drastickymi omezenimi na pamet dela veci ponekud slozitejsim, zatimco platforma Arduino se zuby nehty snazi vse slozite "schovat", napriklad do knihoven, a nebo o tom aspon moc nemluvit a tim se ta omezeni jeste nasobi, ci mozna dokonce umocnuji)

Re: Správná alokace paměti

Napsal: 06 kvě 2018, 03:31
od gilhad
Mimochodem - tohle je docela zajimave cteni, jak jednoduche veci vubec nemusi byt jednoduche :)

https://blog.kriegsman.org/2013/12/01/o ... g-so-am-i/

Re: Správná alokace paměti

Napsal: 06 kvě 2018, 10:47
od michal123
děkuji, myslím ze je to už jasné (moc to nepřehánět), už pár let programuju a znám dost stránek s příklady (a vím, že na některé se prostě nedá spolehnout, pro alokování to bude new a delete). ano pro optimalizaci by bylo krásní mát vedle sebe zdrojová kód a výstup z kompilátoru a vědět která část je kde ale v tuhle chvíli nemám tolik času a ten funkční když dlouhý kód.

A ano dovzdělávám se pořád ale dlouho už jsem nenašel pro mě zajímavý článek pro moje aplikace (ten váš je).

No půjdu to vyzkoušet prakticky.
Mám ještě pár drobných otázek, ale o tech až co si je více vyzkouším.