Å bygge en vektorklasse i Arduino

Jeg sliter med dette problemet som jeg ser ut til å komme meg rundt. Når jeg definerer en vektorklasse, ser jeg ut til å virke for å få noen problemer når du sletter den tildelte pekeren.

Merkelig nok, dette skjer bare etter at jeg «har lest» minnet. Det jeg mener med dette er at hvis jeg skyver en serie verdier tilbake til vektoren, ser de ut til å bli der. Men når jeg har lest verdiene, ser det ut til at datapekeren blir ugyldig av noe slag, så jeg krasjer når jeg prøver å omplassere den.

Kode:

#ifndef VECTOR_H #define VECTOR_H #include <string.h> /* memcpy */ #define min(a,b) (a < b) ? a : b template <typename T> class vector { public: vector() : __capacity(0), __count(0), __data(0) {} //empty constructor vector(T obj, int count = 1) { if (this->__data) { // free data ptr if used delete this->__data; } this->__data = new T[count]; this->__count = count; this->__capacity = count; for (int i = 0; i < count; i++) //populate array with given object this->__data[i] = obj; } ~vector() { if (this->__data) { // free data ptr if used delete [] this->__data; } this->__count = this->__capacity = 0; } T const & operator[] (unsigned int idx) const { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } } T& operator[] (unsigned int idx) { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } } void resize_to_fit() { resize(this->__count); } T& pop_back(){ return this->__data[--this->__count]; } void push_back(T obj) { if (this->__capacity == this->__count) { resize(this->__capacity + 1); } this->__data[this->__count++] = obj; } bool isempty() { return !this->__data || !this->capacity || !this->size; } void clear() { this->~vector(); } T* data() { return this->__data; } int size() { return this->__count; } int capacity() { return this->__capacity; } private: void resize(int capacity) { if (this->__data == nullptr) { this->__data = new T[capacity]; this->__count = 0; this->__capacity = capacity; } else if (capacity != this->__capacity) { //else do nothing T* data = new T[capacity]; this->__count = min(this->__count, capacity); this->__capacity = capacity; memcpy(data, this->__data, sizeof(T) * this->__count); delete this->__data; //program crashes here, but the pointer is already broken... this->__data = data; } } protected: int __capacity; int __count; T* __data; }; #endif//VECTOR_H 

Jeg brukte denne koden i min Arduino, ikke sikker på om den hjelper

void print_vec(vector<int> v){ Serial.println("Printing new vec!"); for( int i = 0; i < v.size(); i++){ Serial.println(v[i]); } } void setup() { // put your setup code here, to run once: Serial.begin(9600); // Open serial connection to report values to host while(!Serial); //Waiting for serial to open vector<int> i = vector<int>(); i.push_back(10); i.push_back(2); print_vec(i); //prints 10 and 2, perfect so far i.push_back(3); while(true){ print_vec(i); i.pop_back(); delay(2000); } } void loop() { // put your main code here, to run repeatedly: } 

Denne koden gir utgang: Utskrift av ny vec! 10 2 Skrive ut ny vec! 0 2 3 Skrive ut ny vec! 0 2 Utskrift av ny vec! 0 (…)

Hva kan forårsake dette? Jeg er steinlagt en stund nå, noe innblikk i hvordan du kan løse dette er verdsatt. Takk!

Kommentarer

  • På slutten av det du si at kodeutgangene er «(…)» en del av utgangen du ser, eller ikke? Hvis den ikke er ‘ t, hvordan skiller utgangen seg fra det du forventet å se? Hvis det er fire elementer i en vektor, skal fire i.pop_back() samtaler tømme vektoren.
  • delete this->__data; //program crashes here ... burde bruke delete[] ikke sant? Det er heller ingen plattformkode i klassen din, så du kan feilsøke på pc der det er mye bedre feilsøkingsverktøy tilgjengelig.

Svar

Du hadde mange problemer med koden din, så det er vanskelig å vite hvor du skal begynne. 🙂

Jeg er enig med det John Burger sa.

Jeg tok koden din med på PCen min for å spare på å laste den opp hver gang, og også slik at jeg kunne bruke valgrind på den. Sikkert rapporterte valgrind en feil etter at du hadde skrevet ut vektoren. Årsaken til det er enkel. Du sendte en kopi av klassen til print_vec som betydde at ødeleggeren fikk ringte når print_vec gikk ut, og dermed fordele minnet. Du burde hatt en kopikonstruktør . Uten den lager kompilatoren en bitvis kopi av klassen, noe som betyr at du nå har to objekter som deler det samme tildelte minnet.

En rask og skitten løsning er å ringe print_vec med referanse:

void print_vec(vector<int> & v){ 

Imidlertid lar buggen lure rundt for fremtiden.

Jeg implementerte kopikonstruktøren i mitt eksempel nedenfor, men når du ringer til print_vec, refererer du til at klassen må kopieres, og reduserer antallet new/delete du gjør, og muligens reduserer minnefragmentering.


Som John Burger sa: ikke kall destruktøren selv! Du kan ikke gjøre det. Hvis du vil gjøre det samme i destruktøren og clear -funksjonen, er det bare å få destruktøren til å ringe clear().


Bruk av ledende dobbeltstreker i variabler er i strid med C ++ -standarden. Ikke gjør det. Bruk et etterfølgende understreking hvis du vil indikere en medlemsvariabel. Mist alle this-> referanser. De roter bare opp ting.


Siden du tildeler en matrise, bør du bruke delete [] – du gjorde det ett sted, men ikke det andre.


Hvorfor gjøre dette? Det er en unødvendig oppgave:

 vector<int> i = vector<int>(); 

Bare gjør:

 vector<int> i; 

Hvis du skal tildele klassen av en eller annen grunn, bør du også implementere en operator=, ellers får du de samme problemene som du hadde med kopikonstruktøren. (Se eksemplet mitt).


Her tester du funksjoner (kapasitet og størrelse) uten å ringe dem:

bool isempty() { return !this->__data || !this->capacity || !this->size; } 

Du kan ikke virkelig gjøre dette :

 T const & operator[] (unsigned int idx) const { if (idx < this->__count) { return this->__data[idx]; } else { // throw exception or handle error to be implemented } } 

Hvis du tar «annet» -banen, returnerer funksjonen ingen verdi (du får en kompilatorvarsel hvis du slår på advarsler).


Mitt omarbeidede eksempel, med forslagene mine i det. Den kompileres uten advarsler eller feil og kjører uten å krasje (på en PC):

// g++ -std=c++11 -g3 test.cpp -o test // OR: clang -std=c++11 -g3 -Wall test.cpp -lstdc++ -o test // // valgrind --leak-check=yes ./test #include <stdio.h> // printf #include <string.h> /* memcpy */ #define min(a,b) (a < b) ? a : b template <typename T> class vector { public: //empty constructor vector() : capacity_(0), count_(0), data_(nullptr) {} // copy constructor vector (const vector & rhs) : capacity_(0), count_(0), data_(nullptr) { data_ = new T [rhs.capacity_]; capacity_ = rhs.capacity_; count_ = rhs.count_; memcpy (data_, rhs.data_, sizeof (T) * count_); } // end of copy constructor // operator= vector & operator= (const vector & rhs) { // gracefully handle self-assignment (eg. a = a;) if (this == &rhs ) return *this; data_ = new T [rhs.capacity_]; capacity_ = rhs.capacity_; count_ = rhs.count_; memcpy (data_, rhs.data_, sizeof (T) * count_); return *this; } // end of operator= // destructor ~vector() { clear (); } // end of destructor T const & operator[] (unsigned int idx) const { return data_[idx]; } T& operator[] (unsigned int idx) { return data_[idx]; } void resize_to_fit() { resize(count_); } T& pop_back(){ return data_[--count_]; } void push_back(T obj) { if (capacity_ == count_) { resize(capacity_ + 1); } data_[count_++] = obj; } bool isempty() { return count_ == 0; } void clear() { delete [] data_; data_ = nullptr; count_ = capacity_ = 0; } T* data() { return data_; } int size() { return count_; } int capacity() { return capacity_; } private: void resize(int capacity) { if (data_ == nullptr) { data_ = new T[capacity]; count_ = 0; capacity_ = capacity; } else if (capacity > capacity_) { //else do nothing // allocate new memory T* data = new T[capacity]; // count can"t be higher than capacity count_ = min(count_, capacity); capacity_ = capacity; // copy data across to new pointer memcpy(data, data_, sizeof(T) * count_); // delete old pointer delete [] data_; // now remember the new pointer data_ = data; } } protected: int capacity_; int count_; T* data_; }; void print_vec(vector<int> v){ printf("%s\n", "Printing new vec!"); for( int i = 0; i < v.size(); i++){ printf("%i\n", v[i]); } } int main() { vector<int> i; i.push_back(10); i.push_back(2); print_vec(i); i.push_back(3); vector<int> j; // test assignment j = i; print_vec(j); print_vec(i); while(i.size () > 0){ print_vec(i); i.pop_back(); } } 

Kommentarer

  • Greit, tusen takk! Mange av problemene var bare distraksjon på slutten, jeg prøvde å fokusere på å fikse minneproblemene jeg sto overfor først.

Svar

I den andre konstruktøren starter du med følgende kode:

if (this->__data) { // free data ptr if used delete this->__data; } // if 

Dette er dødelig! Du har ikke initialisert __data, så den kan holde hvilken som helst verdi under solen. Og det er ingen måte at den kan muligens være en gyldig peker til eksisterende data – det er et helt nytt, ikke-initialisert objekt. Å returnere denne søppelpekeren til haugen ber ganske enkelt om problemer.

Fjern disse linjene – eller enda bedre, bruk en initialiseringsliste som du gjorde med den første konstruktøren:

vector(T obj, int count = 1) : __data(new T[count]), __count(count), __capacity(count) { for (int i = 0; i < count; i++) //populate array with given object __data[i] = obj; } // vector(T, count) 

Et annet problem: I ditt clear() medlem du skrev:

void clear() { this->~vector(); } 

Du burde aldri, aldri , aldri kaller direkte en destruktør som denne. Kompilatoren kan sette alle slags andre koder i destruktorkoden, siden den «vet» at den er den eneste som noen gang vil kalle den. For eksempel skyver noen kompilatorer et flagg for å si om de skal delete objektet fra dyngen etter å ha gjort ødeleggelsen. Det har du ikke gjort, slik at ovennevnte kan ødelegge alle slags ting.

I stedet flytt koden som er i destruktoren inn i clear(), og så er det bare å ringe clear() fra destruktøren.

En annen, denne plukket opp av @Nick Gammon:

bool isempty() { return !this->__data || !this->capacity || !this->size; } 

Dette tester om __data medlemmet er falskt, og om capacity og size funksjoner er definert. Ikke bekymre deg: de to sistnevnte har vært … du savnet prefikset __

[Stopp det også med this-> overalt. Du har brukt en __ før alle medlemsvariablene dine (som i seg selv er mot konvensjonen ); du trenger ikke å hamre hjem det faktum at de er medlemsvariabler: kompilatoren vet allerede.]

Kommentarer

  • Jeg slettet hele vector(T obj, int count = 1) konstruktøren, og den kompilerte uten feil, så det var ikke ‘ t problemet i dette tilfelle, men du har rett i at __data ikke ble initialisert.
  • should never have compiled – den testet funksjonene i stedet for å ringe dem. capacity er en medlemsfunksjon, ikke en variabel. Jeg hentet det da jeg prøvde å kompilere det under clang.
  • Hee hee! Jeg savnet det – og du ‘ har rett!
  • litt råd: Koden din er praktisk talt uleselig. Bruk en bedre kodestil : google.github.io/styleguide/cppguide.html
  • Jeg kan ikke se hvorfor du gjør dette. Mens du er fullblåst STL støttes ikke, det er flere godt feilsøking ed implementeringer som du kan finne med et enkelt google-søk. Hvis du gjør det for å lære C ++, er sannsynligvis arduino-miljøet den verste måten å gjøre det på!

Svar

Det er noe jeg vil legge til.
Andre har allerede påpekt mange feil i koden. Og jeg kunne peke på mange flere, men det er ikke poenget.
IMO, den største feilen er å implementere en Vector-klasse for Arduino!
Selv om det fungerer, er det bare en dårlig idé. Husk at Arduino har veldig begrenset minne. Dynamisk minnetildeling i et så begrenset rom høres bare ut som en dårlig idé – du kaster bort minne, du fragmenterer haugen og du får nesten ingenting, bortsett fra noen kodingskomfort.
Jeg har ennå ikke sett et tilfelle der man virkelig trenger dynamisk minnetildeling i et Arduino-prosjekt. Og selv om du gjør det, er det mange andre verktøy du kan bruke (som stabiltildelte midlertidige buffere, arenaallokeringer, poolallokeringer og hva som helst). Men igjen, sannsynligvis koden og minnekostnaden på disse «generiske» løsningene vil være umulige på Arduino.
Det meste av tiden er det greit å bruke vektorer, kart og hva du vil på plattformer som PC (jeg jobber som programmerer for PC-spill, så jeg støter ofte på situasjoner der statister som disse er store «nei-nei», men for de fleste applikasjoner er det greit).
Men på en «tett» plattform som Arduino, tror jeg du bør holde deg «nær metallet» og være i total kontroll over hva som skjer (minne og CPU-sykluser klokt.) Ideelt sett bør du bare ha råd til «luxuri es «som disse når du vet hva som skjer» under «og du vet at du kan komme unna med det. Noe som sjelden er tilfelle på «trange» plattformer som Arduino.

Kommentarer

  • Du har et poeng her. Spesielt ideen push_back som gradvis tildeler mer minne (som normalt er bra på en PC med 4 Gb RAM), vil trolig innføre minnefragmentering. Jeg tror til og med normale STL-implementeringer tildeler vektorminne (kapasitet i dette tilfellet) i grupper (f.eks. 10 om gangen) for å redusere dette problemet.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *