Jeg spurte tidligere dette spørsmålet:
Er det nødvendig å slette variabler før du går i dvale?
På det spørsmål, @Delta_G postet denne kommentaren:
... Virkelig på en mikrokontroller ville jeg lage objektet i et mindre omfang og prøve å gjøre alt i min makt for å unngå å måtte bruke
ny
eller annen form for dynamisk tildeling. .... osv.
Den kommentaren fikk tre likes, og når jeg googler om dynamisk tildeling ved hjelp av Arduino, alle prøver også å holde seg borte fra det. Oppsummert fra all forskningen jeg gjorde, er konklusjonen min nå Ikke tildel minne hvis du ikke virkelig trenger det .
Jeg bruker Visual Studio IDE for å lage mine C ++ biblioteker som jeg har tenkt å bruke med Arduino. På Arduino IDE refererer jeg bare til disse bibliotekene, og koden kompilerer bra. Visual Studio er veldig kraftig, og det gjør det mulig for meg å lage veldig fin kode, fordi jeg kan teste den på datamaskinen min før jeg kjører den på Arduino. For eksempel opprettet jeg dette biblioteket:
// MyQueue.htypedef struct QueueItem {void * item; QueueItem * neste; QueueItem () {item = nullptr; neste = nullptr; }} QueueItem; class Queue {public: usignert rødtall; / * Antall elementer i kø * / QueueItem * først; / * Peker på første element i køen * / Kø () / * Konstruktør * / {count = 0; først = nullptr; } void enqueue (void * item) / * Enqueue an object into the kø * / {count ++; if (first == nullptr) {first = new QueueItem (); første->item = vare; // Loggmelding fordi vi bruker det "nye" ordet. Vi må sørge for at vi disponerer QueueItem senere #ifdef windows std :: cout << "Opprette" << først << endl; #endif // windows}
annet {// Finn siste element QueueItem * gjeldende = først; mens (nåværende->next! = NULL) {nåværende = nåværende->next; } QueueItem * newItem = new QueueItem (); newItem->item = vare; // Loggmelding fordi vi bruker det "nye" nøkkelordet. Vi må sørge for at vi disponerer QueueItem senere #ifdef windows std :: cout << "Opprette" << newItem << endl; #endif // windows current->next = newItem; }} ugyldig * dequeue () {if (count == 0) return nullptr; QueueItem * newFirst = første->neste; ugyldig * pointerToItem = første->item; // Loggmelding vi sletter et objekt fordi vi opprettet det med det 'nye' nøkkelordet #ifdef windows std :: cout << "Slette" << først << endl; #endif // windows sletter først; først = newFirst; telle--; returpekerToItem; } ugyldig clear () / * Tom kø * / {mens (tell > 0) {dequeue (); }} ~ Kø () / * Destructor. Kast alt * / {clear (); }};
Nå på min Arduino-skisse kan jeg ha følgende kode hvis jeg refererer til toppteksten.
typedef struct Foo {int id; } Foo; ugyldig someMethod () {Kø q; // Lag ting Foo a; a.id = 1; Foo b; b.id = 2; // Enqueue a, b og c q.enqueue (&a); q.enqueue (&b); // Deque Foo * pointerTo_a = (Foo *) q.dequeue (); int x = pointerTo_a->id; // = 1 Foo * pointerTo_b = (Foo *) q.dequeue (); int y = pointerTo_b->id; // = 2 // Feil Foo * test = (Foo *) q.dequeue ();
// test == nullpeker}
De fleste sier ikke bruker ugyldige pekere. Hvorfor!? Fordi jeg bruker ugyldige pekere, kan jeg nå bruke denne køklassen med det objektet jeg vil!
Så jeg antar at spørsmålet mitt er: Hvorfor prøver alle å holde seg borte og unngå kode som denne?
Jeg bruker NRF24L01 radiomodulen til å sende meldinger til flere Arduinos. Det er praktisk å ha en kø med meldinger som skal sendes. Jeg ville være i stand til å kode det samme programmet uten å tildele minne og unngå nøkkelordet new
. Men den koden vil se stygg ut etter min mening.
I denne karantene bestemte jeg meg for å lære C ++, og det har endret måten jeg koder Arduino på. I det øyeblikket jeg lærte C ++ sluttet jeg å bruke Arduino IDE. Jeg har vært støttet utvikler i 12 år, og det er grunnen til at jeg lærte C ++ på et par måneder. Arduino er bare en hobby for meg. Jeg er fortsatt veldig ny for mikrokontrollere, og jeg vil like å forstå hvorfor folk holder seg borte fra C ++ full kraft når det gjelder mikrokontrollere . Jeg vet at jeg bare har 2 kilobyte RAM. Jeg vil ikke tildele så mye minne. Jeg vil fortsatt dra nytte av programmeringsspråket C ++ ved å bruke ny
, slett
, poineters
og destruktorer`. Jeg vil fortsette å bruke Visual Studio til å skrive kraftige C ++ - biblioteker.
I C ++ skriver jeg grensesnitt som dette
// Merk at jeg bruker uint32_t i stedet for 'usignert lang' fordi en usignert lang er annen størrelse på Windows enn på Arduino. Også bruker jeg en usignert kort i stedet for en int fordi en usignert kort er av samme størrelse på Windows og Arduino.class IArduinoMethods {public: // Usignert lang i Arduino virtuell ugyldig forsinkelse (uint32_t delayInMilliseconds) = 0; virtuell ugyldig utskrift (const char * text) = 0; virtuell uint32_t millis () = 0; // Få forløpt tid i millisekunder};
Og så implementerer jeg klassene slik. Dette er for eksempel klassen jeg vil bruke når jeg tester koden min på en Windows-datamaskin:
// Klasse som skal kjøres på Windows.class ArduinoMockWindows: public IArduinoMethods {public: // Arvet via IArduinoMethods virtual void delay (uint32_t delayInMilliseconds) overstyring {// Denne koden vil være annerledes på Arduino, og det er derfor jeg trenger denne avhengigheten Sleep (delayInMilliseconds); // Windows} virtuell uint32_t millis () {// clock_begin = std :: chrono :: steady_clock :: now (); std :: chrono :: steady_clock :: time_point now = std :: chrono :: steady_clock :: now (); automatisk varighet = now.time_since_epoch (); // etc .. return someDuration; }};
Fordi en Windows-datamaskin ikke kan sende NRF24-radiomeldinger, kan jeg implementere et grensesnitt (avhengighet) som vil skrive til en fil, for eksempel i stedet for å sende en ekte radiopakke bare for testing.
Advarselen er at bibliotekene mine vil kreve disse avhengighetene. For at biblioteket mitt skal fungere, må jeg sende det et objekt av typen IArduinoMethods
og INrfRadio
. Hvis jeg kjører koden min på Windows, vil jeg gi den en klasse som vil implementere de metodene som kan kjøres på Windows. Poenget er uansett ikke å vise hvordan C ++ fungerer. Jeg viser bare hvordan jeg bruker pekere og tildeler minne til mange ting.
Fordi jeg tildelte minne, var jeg i stand til å teste biblioteket mitt på Windows og på Arduino for eksempel. Jeg kan også lage enhetstester. Jeg ser så mange fordeler ved å tildele minne. Hvis jeg er organisert og husker å frigjøre gjenstandene jeg ikke lenger bruker, kan jeg få alle disse fordelene. Hvorfor koder folk ikke slik når det gjelder Arduino?
Rediger 1
Nå som jeg forstår hvordan dyng fragmentering fungerer, jeg vet at jeg må være forsiktig når jeg bruker nøkkelordet nytt
.
Jeg hater når folk gjør det de blir bedt om å gjøre uten å forstå hvordan ting fungerer. For eksempel svaret https://arduino.stackexchange.com/a/77078/51226 fra Hvorfor er købiblioteket i dette spørsmålet for det første? . Det kommer til å være tider når en ringebuffer fungerer bedre, og andre ganger når nye
søkeordet fungerer bedre. Sannsynligvis fungerer ringbufferen best i de fleste tilfeller.
Ta følgende scenario hvor du bare har 1 KB minne igjen.
- Det er et hierarki av noder der en node har et barn og et søsken. For eksempel kan node A ha barn B og søsken C. Da kan barn B få et annet barn osv.
(Jeg lagrer dette i minnet)
- Jeg har en kø med arbeid som må gjøres.
(jeg må lagre dette arbeidet et sted)
- Jeg vil ha en kø med hendelser
(jeg må lagre dette et sted)
Hvis jeg bruker det de fleste sier Jeg burde gjøre det, så må jeg:
-
Reserver 500 kB for å kunne lagre noder (jeg vil være begrenset til n antall noder)
-
Reserver 250 kB for arbeidskøen som må gjøres.
-
Reserve 250 kB for køen av hendelser.
Dette er hva folk flest vil gjøre og det vil fungere bra uten problemer med haugfragmentering.
Nå Dette er hva jeg vil gjøre
-
Forsikre deg om at alt jeg tildeler er av størrelse 12 byte. En node har bare id (usignert int), underordnet (peker), type (usignert tegn) osv. Med totalt 12 byte.
-
Sørg for at alle arbeid som skal innhentes er også av størrelse 12 byte.
-
Forsikre deg om at alle hendelsene som blir innhentet også er av størrelse 12 byte.
Nå hvis jeg har mer arbeid enn arrangementer, vil dette fungere. Jeg må bare programmere i koden min at jeg aldri tildeler mer enn 70 artikler. Jeg vil ha en global variabel som har det antallet tildelinger. Koden min blir mer fleksibel. Jeg trenger ikke å sitte fast med strengt 20 hendelser, 20 arbeid og 30 noder. Hvis jeg har færre noder, vil jeg kunne ha flere hendelser. ** Uansett poenget mitt er at den ene løsningen ikke er bedre enn den andre. Det kommer til å være scenarier når en løsning er bedre.
Avslutningsvis er det bare å forstå hvordan haugfragmentering fungerer, og du vil få mye kraft ved å bruke det nye nøkkelordet. Ikke vær en sau og gjør det folk ber deg om å gjøre uten å forstå hvordan ting fungerer. **.
Rediger 2
Takk til @EdgarBonet , Jeg endte opp med å lagre noder på bunken. Dette er hvorfor:
Jeg har et hierarki av noder som kan representeres som:
typedef struct Node {usignert kort id; Node * søsken; Node * underordnet;} Node;
Som du ser er hver node bare 6 byte. Det er en annen grunn til at jeg ikke brydde meg så mye om å tildele noder i begynnelsen. Hvis jeg tildeler denne noden på bunken, mister jeg 2 byte til (33%) for hver tildeling, fordi størrelsen på noden må lagres på hver tildeling. Som et resultat opprettet jeg disse to metodene og en buffer:
// For at dette skal fungere, kan en node aldri ha en id på 0 !!! Node nodeBuffer [50]; / * Buffer for å lagre noder på stabel * / Node * allocateNode (Node nodeToAllocate) / * Metode for å lagre en node * / {// Finn første tilgjengelige sted der en node kan lagres for (char i = 0; i < 50; i ++) {if (nodeBuffer [i] .id == 0) {nodeBuffer [i] = nodeToAllocate; returner & nodeBuffer [i]; }} return nullptr;} void freeNode (Node * nodeToFree) / * Metode for å slette en node * / {
nodeToFree->id = 0; // Hvis ID-en til en node er 0, er dette min konvensjon å vite at den blir slettet.}
Og på koden min pleide jeg å ha ting som:
Node * a = new Node (); a->id = 234423; // .... // .. etc // ..slett a;
Nå har jeg bare må erstatte den koden med:
Node * a = allocateNode ({}); a->id = 234423; // .... // .. etc // ..freeNode ( a);
Og koden min fungerer nøyaktig den samme uten å måtte bruke det nye
nøkkelordet. Jeg trodde det skulle bli komplisert å refaktorere koden og lage en buffer.
Jeg gjorde denne endringen fordi jeg ønsket å kunne lagre flere noder på koden min. Ved å miste de 33% klarte jeg ikke å skape så mange. Hvis jeg bare tildeler objekter av samme størrelse og ikke tildeler så mange, er det helt greit å bruke nøkkelordet nytt
. > Også i tilfelle køen vil jeg tildele og slette objekter veldig raskt. Fordi objektene ikke vil vare på minnet for lenge, og sjansene for å ha haugfragmentering er svært lave.