Fråga:
Är att använda malloc () och gratis () en riktigt dålig idé på Arduino?
Cybergibbons
2014-03-09 14:00:25 UTC
view on stackexchange narkive permalink

Användningen av malloc () och free () verkar ganska sällsynt i Arduino-världen. Den används i ren AVR C mycket oftare, men ändå med försiktighet.

Är det en riktigt dålig idé att använda malloc () och free () kod> med Arduino?

du kommer att ta slut på minnet väldigt snabbt annars, och om du vet hur mycket minne du använder kan du lika gärna tilldela det statiskt ändå
Jag vet inte om det är * dåligt *, men jag tror att det inte används eftersom du nästan aldrig tar slut på RAM för de flesta skisser och det är bara slöseri med blixt och värdefulla klockcykler. Glöm inte räckvidden (även om jag inte vet om det utrymmet fortfarande är tilldelat för alla variabler).
Som vanligt är rätt svar "det beror". Du har inte lämnat tillräckligt med information för att säkert veta om dynamisk tilldelning är rätt för dig.
åtta svar:
JRobert
2014-03-11 21:36:25 UTC
view on stackexchange narkive permalink

Min allmänna regel för inbäddade system är att endast malloc () stora buffertar och bara en gång, i början av programmet, t.ex. i setup () . Problemet uppstår när du tilldelar och avdelar minne. Under en långsiktig session blir minnet fragmenterat och så småningom misslyckas en allokering på grund av brist på ett tillräckligt stort ledigt område, även om det totala lediga minnet är mer än tillräckligt för begäran.

(Historiskt perspektiv, hoppa över om du inte är intresserad): Beroende på lastarimplementeringen är den enda fördelen med körtidstilldelning jämfört med kompileringstidsallokering (intialiserade globaler) storleken på hex-filen. När inbyggda system byggdes med hylldatorer med allt flyktigt minne överfördes programmet ofta till det inbäddade systemet från ett nätverk eller en instrumentdator och uppladdningstiden var ibland ett problem. Att lämna buffertar fulla av nollor från bilden kan förkorta tiden avsevärt.)

Om jag behöver dynamisk minnestilldelning i ett inbäddat system, malloc () , eller helst, statiskt allokera, en stor pool och dela den i buffertar med fast storlek (eller en pool vardera med små respektive stora buffertar) och gör min egen tilldelning / avdelning från den poolen. Därefter hedras varje begäran om vilken mängd minne som helst upp till den fasta buffertstorleken med en av dessa buffertar. Samtalsfunktionen behöver inte veta om den är större än begärt, och genom att undvika att dela och kombinera block löser vi fragmentering. Naturligtvis kan minnesläckor fortfarande uppstå om programmet har allokera / avdela fel.

En annan historisk anmärkning, detta ledde snabbt till BSS-segmentet, vilket gjorde det möjligt för ett program att nollställa sitt eget minne för initialisering utan att långsamt kopiera nollorna under programbelastningen.
Edgar Bonet
2015-03-01 19:51:45 UTC
view on stackexchange narkive permalink

Jag har tittat på algoritmen som används av malloc () , från avr-libc, och det verkar finnas några användningsmönster som är säkra ur heapfragmenteringens synvinkel:

1. Tilldela endast långlivade buffertar

Med detta menar jag: fördela allt du behöver i början av programmet och frigör det aldrig. Naturligtvis, i det här fallet kan du lika gärna använda staticbuffers ...

2. Tilldela endast kortlivade buffertar

Betydelse: du frigör bufferten innan du tilldelar något annat. Areasonable-exempel kan se ut så här:

  void foo () {size_t size = figure_out_needs (); char * buffert = malloc (storlek); om (! buffert) misslyckas (); do_whatever_with (buffert); gratis (buffert);}  

Om det inte finns någon malloc inuti do_whatever_with () , eller om den funktionen fritt vad den tilldelar, är du säker från fragmentering.

3. Frigör alltid den senast tilldelade bufferten

Detta är en generalisering av de två tidigare fallen. Om du använder heaplike en stack (sista in är först ut), kommer den att fungera som en stack och inte fragment. Det bör noteras att i det här fallet är det säkert att tora storlek på den senast tilldelade bufferten med realloc().

4. Tilldela alltid samma storlek

Detta förhindrar inte fragmentering, men det är säkert i den meningen att högen inte blir större än den maximala använda storleken. Om alla dina buffertar har samma storlek kan du vara säker på att, när du frigör en av dem, kommer platsen att finnas tillgänglig för efterföljande tilldelningar.

Mönster 2 bör undvikas eftersom det lägger till cykler för malloc () och gratis () när detta kan göras med "char buffert [storlek];" (i C ++). Jag skulle också vilja lägga till antimönstret "Aldrig från en ISR".
jfpoilpret
2014-03-09 22:07:19 UTC
view on stackexchange narkive permalink

När du skriver Arduino-skisser kommer du vanligtvis att undvika dynamisk fördelning (vare sig det är med malloc eller new för C ++ -instanser), människor använder hellre global -eller statiska - variabler eller lokala (stack) variabler.

Att använda dynamisk fördelning kan leda till flera problem:

  • minnesläckor (om du tappar en pekare till ett minne som du tidigare tilldelat, eller mer sannolikt om du glömmer att frigöra det tilldelade minnet när du inte behöver det längre)
  • höguppdelning (efter flera malloc / gratis -samtal) där högen blir större än den faktiska mängden minne som tilldelas för närvarande

I de flesta situationer jag har mött var dynamisk tilldelning antingen inte nödvändig eller kunde undvikas med makron som i följande kodexempel:

MySketch.ino

  #define BUFFER_SIZE 32 # include "Dummy.h"  

Dummy.h)

  klass Dummy {byte buffert [BUFFER_SIZE]; ...};  

Utan #define BUFFER_SIZE , om vi ville att Dummy -klassen skulle ha en icke-fast -buffert storlek, skulle vi behöva använda dynamisk allokering enligt följande:

  class Dummy {const byte * buffer; public: Dummy (int-storlek): buffert (ny byte [storlek]) {} ~ Dummy () {delete [] bufer; }};  

I det här fallet har vi fler alternativ än i det första exemplet (t.ex. använda olika Dummy -objekt med olika -buffert storlek för varje), men vi kan ha problem med höguppdelning.

Observera att en destruktor används för att säkerställa att dynamiskt allokerat minne för buffert frigörs när en Dummy / code> instans raderas.

Peter Bloomfield
2014-03-09 23:32:04 UTC
view on stackexchange narkive permalink

Att använda dynamisk tilldelning (via malloc / gratis eller ny / radera ) är inte i sig dåligt eftersom sådan. Faktum är att för något som strängbearbetning (t.ex. via String -objektet) är det ofta ganska användbart. Det beror på att många skisser använder flera små fragment av strängar, som så småningom kombineras till en större. Med hjälp av dynamisk fördelning kan du bara använda så mycket minne som du behöver för var och en. Däremot kan användning av en statisk buffert i fast storlek för var och en sluta slösa mycket utrymme (orsaka att minnet tar slut mycket snabbare), även om det helt beror på sammanhanget.

Med allt av det sagt är det mycket viktigt att se till att minnesanvändningen är förutsägbar. Att låta skissen använda godtyckliga mängder minne beroende på körningstid (t.ex. ingång) kan lätt orsaka problem förr eller senare. I vissa fall kan det vara helt säkert, t.ex. om du vet kommer användningen aldrig att lägga upp för mycket. Skisser kan ändras under programmeringsprocessen. Ett antagande som gjorts tidigt kan glömmas bort när något ändras senare, vilket resulterar i ett oförutsett problem.

För robusthet är det oftast bättre att arbeta med buffertar i fast storlek där det är möjligt, och utforma skissen för att fungera uttryckligen med dessa gränser från början. Det innebär att framtida ändringar av skissen eller eventuella oväntade körtidsförhållanden förhoppningsvis inte skulle orsaka några minnesproblem.

StuffAndyMakes
2015-05-18 19:49:12 UTC
view on stackexchange narkive permalink

Jag håller inte med människor som tycker att du inte borde använda det eller annars är det i allmänhet onödigt. Jag tror att det kan vara farligt om du inte känner till det, men det är användbart. Jag har fall där jag inte vet (och inte bryr mig om att veta) storleken på en struktur eller en buffert (vid sammanställningstid eller körtid), speciellt när det gäller bibliotek jag skickar ut i världen. Jag håller med om att om din applikation bara har att göra med en enda, känd struktur, borde du bara baka i den storleken vid sammanställningstid.

Exempel: Jag har en seriepaketklass (ett bibliotek) som kan ta godtyckliga data nyttolaster (kan vara struktur, array av uint16_t, etc.). Vid sändningsänden för den klassen berättar du helt enkelt Packet.send () -metoden adressen till det du vill skicka och HardwareSerial-porten genom vilken du vill skicka den. Men i mottagande änden behöver jag en dynamiskt allokerad mottagningsbuffert för att hålla den inkommande nyttolasten, eftersom den nyttolasten kan vara en annan struktur vid varje givet tillfälle, beroende på applikationens tillstånd, till exempel. Om jag bara någonsin skickar en enda struktur fram och tillbaka skulle jag bara göra bufferten till den storlek som den behöver vara vid sammanställningstid. Men i de fall paket kan ha olika längder över tiden är malloc () och gratis () inte så dåliga.

Jag har kört tester med följande kod i flera dagar och låter den gå kontinuerligt, och jag har inte hittat några bevis på minnesfragmentering. Efter att ha frigjort det dynamiskt tilldelade minnet återgår den fria mängden till sitt tidigare värde. () {extern int __heap_start, * __ brkval; på tv; returnera (int) &v - (__brkval == 0? (int) &__heap_start: (int) __brkval);} uint8_t * _tester; medan (1) {uint8_t len ​​= slumpmässig (1, 1000); Serial.println ("-------------------------------------"); Serial.println ("len är" + Sträng (len, DEC)); Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("_ tester =" + Sträng ((uint16_t) _tester, DEC)); Serial.println ("alloating _tester memory"); _tester = (uint8_t *) malloc (len); Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("_ tester =" + Sträng ((uint16_t) _tester, DEC)); Serial.println ("Fyllning _tester"); för (uint8_t i = 0; i < len; i ++) {_tester [i] = 255; } Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("frigör _testminne"); gratis (_tester); _tester = NULL; Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("_ tester =" + Sträng ((uint16_t) _tester, DEC)); fördröjning (1000); // snabb titt}

Jag har inte sett någon form av nedbrytning i RAM eller i min förmåga att allokera den dynamiskt med den här metoden, så jag skulle säga att det är ett livskraftigt verktyg. FWIW.

Din testkod överensstämmer med användningsmönstret _2. Tilldela endast kortlivade buffertar_ jag beskrev i mitt tidigare svar. Detta är ett av de få användningsmönstren som är kända för att vara säkra.
Med andra ord kommer problemen att uppstå när du börjar dela processorn med annan * okänd * kod - vilket är just det problemet du tror att du undviker. I allmänhet, om du vill ha något som alltid kommer att fungera eller annars misslyckas under länkningen, gör du en fast tilldelning av den maximala storleken och använder den om och om igen, till exempel genom att låta användaren skicka in den till dig vid initialisering. Kom ihåg att du vanligtvis kör på ett chip där ** allt ** måste passa i 2048 byte - kanske mer på vissa brädor men också kanske mycket mindre på andra.
@EdgarBonet Ja, exakt. Ville bara dela.
@ChrisStratton Det är på grund av minnesbegränsningar som jag tror att det är perfekt att tilldela bufferten av den storlek som krävs just nu. Om bufferten inte behövs under vanlig drift är minnet tillgängligt för något annat. Men jag ser vad du säger: Tilldela ett reserverat utrymme så att något annat inte tar bort det utrymme du kan behöva för bufferten. All bra information och tankar.
Det är riskabelt att allokera en buffert med den storlek som behövs dynamiskt, som om något annat tilldelas innan du frigör den kan du ha fragmentering - minne som du inte kan återanvända. Dynamisk allokering har också spårningskostnader. Fast tilldelning betyder inte att du inte kan multiplicera med minnet, det betyder bara att du måste dela delningen i utformningen av ditt program. För en buffert med rent lokalt omfång kan du också väga användningen av stacken. Du har inte kollat ​​på möjligheten att malloc () misslyckas heller.
@ChrisStratton Rätt, den biten av kod kontrollerar inte om malloc () fungerar eller inte. Sedan använder den biten av kända noterade fett Arduino-bibliotek och rutiner. :) Tack för din insikt. Mycket rimligt och värdefullt.
"det kan vara farligt om du inte känner till in- och utgångarna av det, men det är användbart." summerar ganska mycket all utveckling i C / C ++. :-)
Mikael Patel
2017-10-19 15:55:16 UTC
view on stackexchange narkive permalink

Är det en riktigt dålig idé att använda malloc () och free () med Arduino?

Det korta svaret är ja. Nedan följer anledningarna till varför:

Det handlar om att förstå vad en MPU är och hur man programmerar inom begränsningarna för tillgängliga resurser. Arduino Uno använder en ATmega328p MPU med 32KB ISP-flashminne, 1024B EEPROM och 2KB SRAM. Det är inte mycket minnesresurser.

Kom ihåg att 2KB SRAM används för alla globala variabler, stränglitteraler, stack och möjlig användning av heapen. Stapeln måste också ha utrymme för en ISR.

minneslayouten är:

SRAM map

Dagens PC / bärbara datorer har mer än 1.000.000 gånger mängden minne. Ett standardstapelutrymme på 1 Mbyte per tråd är inte ovanligt men helt orealistiskt på en MPU.

Ett inbäddat mjukvaruprojekt måste göra en resursbudget. Detta beräknar ISR-latens, nödvändigt minnesutrymme, beräkningsstyrka, instruktionscykler etc. Det finns tyvärr inga gratis luncher och hård realtidsinbäddad programmering är den mest svåra programmeringsförmågan att bemästra. / p>

Amen till det: "[H] ard realtidsinbäddad programmering är det svåraste att programmera färdigheter att bemästra."
Är verkställandetiden för malloc alltid densamma? Jag kan föreställa mig att malloc tar mer tid när den söker längre i den tillgängliga ram efter en plats som passar? Detta skulle vara ännu ett argument (förutom att rinna ut) för att inte fördela minne när du är på språng?
@Paul Heapalgoritmerna (malloc och gratis) är vanligtvis inte konstant exekveringstid och inte återintressanta. Algoritmen innehåller sök- och datastrukturer som kräver lås vid användning av trådar (samtidighet).
Kelly S. French
2019-05-09 21:31:11 UTC
view on stackexchange narkive permalink

Okej, jag vet att det här är en gammal fråga, men ju mer jag läser igenom svaren, desto mer kommer jag hela tiden tillbaka till en observation som verkar framträdande.

Det stoppande problemet är verkligt

Det verkar finnas en länk till Turing's Halting Problem här. Att tillåta dynamisk fördelning ökar oddsen för att "stoppa" så frågan blir en risktolerans. Även om det är bekvämt att vinka bort möjligheten att malloc () misslyckas och så vidare, är det fortfarande ett giltigt resultat. Frågan som OP ställer verkar bara handla om teknik, och ja, detaljerna i de använda biblioteken eller den specifika MPU spelar roll; konversationen vänder sig mot att minska risken för att programmet stoppas eller något annat onormalt slut. Vi måste erkänna förekomsten av miljöer som tolererar risker väldigt annorlunda. Mitt hobbyprojekt att visa vackra färger på en LED-remsa kommer inte att döda någon om något ovanligt händer men MCU inuti en hjärt-lungmaskin kommer sannolikt att göra det.

Hej Mr. Turing Jag heter Hubris

För min LED-strip bryr jag mig inte om den låser sig, jag återställer den bara. Om jag var på en hjärt-lungmaskin som styrs av en MCU är konsekvenserna av att den låser sig eller inte fungerar bokstavligen liv och död, så frågan om malloc () och gratis () bör delas mellan hur det avsedda programmet hanterar möjligheten att visa Mr. Turing's berömda problem. Det kan vara lätt att glömma att det är ett matematiskt bevis och att övertyga oss om att om vi bara är smarta så kan vi undvika att drabbas av beräkningsgränserna.

Denna fråga borde ha två accepterade svar, en för dem som tvingas blinka när de stirrar The Halting Problem i ansiktet och en för alla andra. Medan de flesta användningarna av arduino sannolikt inte är affärskritiska eller liv-och-död-applikationer, är skillnaden fortfarande kvar oavsett vilken MPU du kan koda.

Jag tror inte att Halting-problemet gäller i denna specifika situation med tanke på att heapanvändningen inte nödvändigtvis är godtycklig. Om det används på ett väldefinierat sätt blir heapanvändningen förutsägbart "säker". Poängen med Halting-problemet var att ta reda på om det kan bestämmas vad som händer med en nödvändigtvis godtycklig och inte så väldefinierad algoritm. Det gäller verkligen mycket mer för programmering i bredare mening och som sådan tycker jag att det är särskilt inte särskilt relevant här. Jag tycker inte ens att det är relevant alls att vara helt ärlig.
Jag ska erkänna en del retorisk överdrift, men poängen är egentligen om du vill garantera beteende, att använda högen innebär en risknivå som är mycket högre än att bara hålla sig till stacken.
JSON
2015-03-01 13:07:39 UTC
view on stackexchange narkive permalink

Nej, men de måste användas mycket noggrant när det gäller att frigöra tilldelat minne. Jag har aldrig förstått varför folk säger att direkt minneshantering bör undvikas eftersom det innebär en nivå av inkompetens som i allmänhet är oförenlig med programutveckling. / p>

Låt oss säga att du använder din arduino för att styra en drönare. Alla fel i någon del av din kod kan potentiellt få den att falla ut ur himlen och skada någon eller något. Med andra ord, om någon saknar kompetensen att använda malloc, borde de troligen inte kodas alls eftersom det finns så många andra områden där små buggar kan orsaka allvarliga problem.

Är det svårare att spåra och fixa buggar orsakade av malloc? Ja, men det är mer en fråga om frustration från kodarnas sida snarare än risk. När det gäller risken kan någon del av din kod vara lika eller mer riskfylld än malloc om du inte vidtar åtgärderna för att se till att den görs rätt.

Det är intressant att du använde en drönare som ett exempel. Enligt denna artikel (http://mil-embedded.com/articles/justifiably-apis-militaryaerospace-embedded-code/), "På grund av sin risk är dynamisk minnestilldelning förbjuden, enligt DO-178B-standarden, i säkerhet -kritisk inbäddad avionik-kod. "
DARPA har en lång historia av att tillåta entreprenörer att utveckla specifikationer som passar deras egen plattform - varför skulle de inte när det är skattebetalare som betalar räkningen. Det är därför det kostar 10 miljarder dollar för dem att utveckla vad andra kan göra med 10 000 dollar. Det låter nästan som om du använder det militära industrikomplexet som en ärlig referens.
Dynamisk allokering verkar som en inbjudan till ditt program för att visa gränserna för beräkning som beskrivs i Halting Problem. Det finns vissa miljöer som kan hantera en liten risk för sådant stopp och det finns miljöer (rymd, försvar, medicin, etc.) som inte tål någon kontrollerbar risk, vilket innebär att de inte tillåter operationer som "inte borde" misslyckas eftersom "det ska fungera" är inte tillräckligt bra när du skjuter upp en raket eller kontrollerar en hjärt- / lungmaskin.


Denna fråga och svar översattes automatiskt från det engelska språket.Det ursprungliga innehållet finns tillgängligt på stackexchange, vilket vi tackar för cc by-sa 3.0-licensen som det distribueras under.
Loading...