Opbygning af en Vector-klasse i Arduino

Jeg kæmper med dette emne, som jeg kan synes at komme rundt på. Når jeg definerer en vektorklasse, synes jeg for at få nogle problemer, når du sletter den tildelte markør.

Mærkeligt nok sker dette kun, når jeg “læser” hukommelsen. Hvad jeg mener med dette er, at hvis jeg skubber en række værdier tilbage til vektoren, ser de ud til at blive der. Men når jeg først har læst værdierne, synes datapekeren at være ugyldig af en eller anden slags, så jeg går ned, når jeg prøver at deallokere 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 brugte denne kode i min Arduino, ikke sikker på, om den hjælper

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 kode output: Udskrivning af ny vec! 10 2 Udskrivning af ny vec! 0 2 3 Udskrivning af ny vec! 0 2 Udskrivning af ny vec! 0 (…)

Hvad kan forårsage dette? Jeg er stenlagt et stykke tid nu, enhver indsigt i, hvordan man løser dette, værdsættes. Tak!

Kommentarer

  • I slutningen af hvad du sig, at kodeudgangene er “(…)” en del af det output, du ser, eller ej? Hvis det ikke er ‘ t, hvordan adskiller output sig fra det, du forventede at se? Hvis der er fire elementer i en vektor, skal fire i.pop_back() opkald tømme vektoren.
  • delete this->__data; //program crashes here ... skal du bruge delete[] rigtigt? Der er heller ingen platformskode i din klasse, så du kan debugge på pc, hvor der er meget bedre fejlretningsværktøjer til rådighed.

Svar

Du havde mange problemer med din kode, så det er svært at vide, hvor du skal starte. 🙂

Jeg er enig med, hvad John Burger sagde.

Jeg tog din kode med på min pc for at spare på at uploade den hver gang, og også så jeg kunne bruge valgrind på den. Bestemt rapporterede valgrind en fejl, efter at du havde udskrevet vektoren. Årsagen til det er enkel. Du sendte en kopi af klassen til print_vec hvilket betød, at destruktøren fik kaldes, når print_vec blev afsluttet, hvorved hukommelsen blev fordelt. Du skulle have haft en kopikonstruktør . Uden det laver kompilatoren en bitvis kopi af klassen, hvilket betyder, at du nu har to objekter, der deler den samme tildelte hukommelse.

En hurtig og beskidt løsning er at kalde print_vec med reference:

void print_vec(vector<int> & v){ 

Men det lader bugten lure rundt i fremtiden.

Jeg implementerede kopibyggeren i mit eksempel nedenfor, men ved at kalde print_vec som reference gemmes klassen, der skal kopieres, hvilket reducerer antallet af new/delete, du laver, og derved muligvis reducerer hukommelsesfragmentering.


Som John Burger sagde: ring ikke til destruktøren selv! Dig kan ikke gøre det. Hvis du vil gøre det samme i destruktoren og clear -funktionen, skal du bare få destruktøren til at ringe til clear().


Brug af førende dobbelt understregning i variabler er i modstrid med C ++ – standarden. Gør det ikke. Brug et efterfølgende understregning, hvis du vil angive en medlemsvariabel. Mist alle this-> referencer. De kludrer bare på tingene.


Da du tildeler en matrix, skal du bruge delete [] – du gjorde det ét sted, men ikke det andet.


Hvorfor gøre det? Det er en unødvendig opgave:

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

Gør bare:

 vector<int> i; 

Hvis du vil tildele klassen af en eller anden grund, skal du også implementere en operator=, ellers har du de samme problemer som du havde med kopibyggeren. (Se mit eksempel).


Her tester du funktioner (kapacitet og størrelse) uden at kalde dem:

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

Du kan ikke virkelig gø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 tager stien “ellers”, returnerer funktionen ingen værdi (du får en advarsel om kompilering, hvis du aktiverer advarsler).


Mit omarbejdede eksempel med mine forslag i det. Den kompileres uden advarsler eller fejl og kører uden at gå ned (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

  • Okay, mange tak! Mange af problemerne var simpelthen distraktion i min ende, jeg prøvede at fokusere på at løse de hukommelsesproblemer, jeg først stod overfor.

Svar

I din anden konstruktør starter du med følgende kode:

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

Dette er dødbringende! Du har ikke initialiseret __data, så den kunne holde enhver værdi under solen. Og der er ingen måde at den kunne muligvis være en gyldig markør til eksisterende data – det er et helt nyt ikke-initialiseret objekt. At returnere denne skraldemarkør til bunken beder simpelthen om problemer.

Fjern disse linjer – eller endnu bedre, brug en initialiseringsliste som du gjorde med den første konstruktør:

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 andet problem: I dit clear() medlem, du skrev:

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

Du bør aldrig, aldrig , aldrig kalder direkte en destruktør som denne. Compileren kan sætte alle mulige andre koder i destruktorkoden, da den “ved”, at den “er den eneste, der nogensinde vil kalde den. For eksempel skubber nogle compilere et flag for at sige, om de skal delete objektet fra bunken efter ødelæggelsen. Du har ikke gjort det, så ovenstående kan ødelægge alle mulige ting.

Flyt i stedet koden, der er i destruktoren ind i clear(), og så skal du blot ringe til clear() fra destruktøren.

En anden, denne tog op af @Nick Gammon:

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

Dette tester, om __data medlem er falsk, og om capacity og size funktioner er defineret. Vær ikke bekymret: de to sidstnævnte har været … du savnede præfikset __

[Stop det også med this-> overalt. Du har brugt en __ før alle dine medlemsvariabler (hvilket i sig selv er imod konventionen ); du behøver ikke at hamre hjem det faktum, at de er medlemsvariabler: kompilatoren ved det allerede.]

Kommentarer

  • Jeg slettede hele vector(T obj, int count = 1) konstruktøren, og den kompilerede uden fejl, så det var ikke ‘ t problemet i dette tilfældet, men du har ret i, at __data ikke blev initialiseret.
  • should never have compiled – det testede funktionerne i stedet for at ringe dem. capacity er en medlemsfunktion, ikke en variabel. Jeg hentede det, da jeg prøvede at kompilere det under clang.
  • Hee hee! Jeg savnede det – og du ‘ har ret!
  • lidt råd: Din kode er praktisk talt uleselig. Brug en bedre kodestil : google.github.io/styleguide/cppguide.html
  • Jeg kan heller ikke se, hvorfor du gør dette. Mens du er fuldt ud STL understøttes ikke, der er flere godt debuggere ed implementeringer, som du kan finde med en simpel google-søgning. Hvis du gør det for at lære C ++, er arduino-miljøet sandsynligvis den værste måde at gøre det på!

Svar

Der er noget, jeg gerne vil tilføje.
Andre har allerede påpeget adskillige fejl i koden. Og jeg kunne pege på mange flere, men det er ikke meningen.
IMO, den største fejl er at implementere en Vector-klasse til Arduino!
Selvom det fungerer, er det bare en dårlig idé. Husk, at Arduino har meget begrænset hukommelse. Dynamisk hukommelsesallokering i et så begrænset rum lyder bare som en dårlig idé – du spilder hukommelse, du fragmenterer bunken, og du får næsten intet, undtagen en vis kodningsbekvemmelighed.
Jeg har endnu ikke set et tilfælde, hvor man virkelig har brug for dynamisk hukommelsestildeling i et Arduino-projekt. Og selvom du gør det, er der mange andre værktøjer, du kan bruge (som stakallokerede temp-buffere, arenaallokeringer, poolallokeringer og hvad der ikke er). Men igen sandsynligvis koden og hukommelsesomkostningerne disse “generiske” løsninger vil være umulige på Arduino.
Det meste af tiden er det fint at bruge vektorer, kort og hvad du vil på platforme som pc (jeg arbejder som pc-programmer, så jeg støder ofte på situationer hvor ekstramateriale som disse er store “nej-nej”, men for de fleste applikationer er det fint).
Men på en “tæt” platform som Arduino, synes jeg du skal holde dig “tæt på metallet” og være i total kontrol over, hvad der foregår (hukommelse og CPU-cyklusser klogt). Ideelt set skulle du kun have råd til “luxuri es “som disse, når du ved, hvad der foregår” nedenunder “, og du ved, at du kan slippe af med det. Hvilket sjældent er tilfældet på “stramme” platforme som Arduino.

Kommentarer

  • Du har et punkt her. Især push_back -idéen, der gradvis tildeler mere hukommelse (hvilket normalt er fint på en pc med 4 Gb RAM), vil sandsynligvis indføre hukommelsesfragmentering. Jeg tror, at selv normale STL-implementeringer tildeler vektorhukommelse (kapacitet i dette tilfælde) i batches (f.eks. 10 ad gangen) for at reducere dette problem.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *