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?
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?
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.
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:
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 ...
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.
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()
.
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.
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:
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
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.
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.
Ä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:
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>
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 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.
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.
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.