Att bygga en vektorklass i Arduino

Jag kämpar med den här frågan som jag kan tycka komma runt. När jag definierar en vektorklass verkar jag för att få problem när du raderar den tilldelade pekaren.

Konstigt nog, detta händer bara efter att jag ”läst” minnet. Vad jag menar med detta är att om jag skjuter tillbaka en serie värden till vektorn verkar de stanna kvar. Men när jag har läst värdena verkar datapekaren bli ogiltig av något slag så jag kraschar när jag försöker placera den.

Kod:

#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 

Jag använde den här koden i min Arduino, inte säker 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: } 

Denna kod matar ut: Utskrift av ny vec! 10 2 Skriva ut ny vec! 0 2 3 Skriva ut ny vec! 0 2 Utskrift av ny vec! 0 (…)

Vad kan orsaka detta? Jag är stenhuggna ett tag nu, alla insikter om hur man löser detta uppskattas. Tack!

Kommentarer

  • I slutet av vad du säg att kodutmatningarna är ”(…)” en del av utgången som du ser eller inte? Om den inte är ’ t, hur skiljer sig utdata från vad du förväntade dig att se? Om det finns fyra objekt i en vektor, ska fyra i.pop_back() samtal tömma vektorn.
  • delete this->__data; //program crashes here ... ska använda delete[] rätt? Det finns inte heller någon plattformskod i din klass så du kan felsöka på pc där det finns mycket bättre felsökningsverktyg tillgängliga.

Svar

Du hade många problem med din kod så det är svårt att veta var du ska börja. 🙂 Visst valgrind rapporterade ett fel efter att du skrivit ut vektorn. Anledningen till det är enkel. Du skickade en kopia av klassen till print_vec vilket innebar att förstöraren fick anropas när print_vec avslutades och därmed omlokaliserar minnet. Du borde ha haft en kopikonstruktör . Utan det gör kompilatorn en bitvis kopia av klassen, vilket innebär att du nu har två objekt som delar samma tilldelade minne.

En snabb och smutsig fix är att ringa print_vec med referens:

void print_vec(vector<int> & v){ 

Men det gör att felet lurar för framtiden.

Jag implementerade kopikonstruktören i mitt exempel nedan, men att ringa print_vec som referens sparar dock klassen som måste kopieras, vilket minskar antalet new/delete som du gör, vilket möjligen minskar minnesfragmentering.


Som John Burger sa: ring inte förstöraren själv! Du kan inte göra det. Om du vill göra samma sak i förstöraren och clear -funktionen, får bara förstöraren att ringa clear().


Användningen av ledande dubbelstreck i variabler strider mot C ++ – standarden. Gör det inte. Använd en efterföljande understrykning om du vill ange en medlemsvariabel. Förlora alla this-> referenser. De förstör bara saker och ting.


Eftersom du tilldelar en matris bör du använda delete [] – du gjorde det på ett ställe men inte på det andra.


Varför gör det här? Det är en onödig uppgift:

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

Gör bara:

 vector<int> i; 

Om du kommer att tilldela klassen av någon anledning bör du också implementera en operator= eller så får du samma problem som du hade med kopikonstruktören. (Se mitt exempel).


Här testar du funktioner (kapacitet och storlek) utan att anropa dem:

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

Du kan inte göra det här :

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

Om du tar ”annars” -vägen returnerar funktionen inget värde (du får en kompilatorvarning om du aktiverar varningar).


Mitt omarbetade exempel med mina förslag i det. Den kompileras utan varningar eller fel och körs utan att krascha (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

  • Okej, tack så mycket! Många av problemen var helt enkelt distraktion i mitt slut, jag försökte fokusera på att åtgärda de minnesproblem som jag stod inför först. class = ”answer”>

    I din andra konstruktör börjar du med följande kod:

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

    Detta är dödligt! Du har inte initierat __data, så det kan hålla något värde under solen. Och det finns inget sätt att det kan möjligen vara en giltig pekare till befintlig data – det är ett helt nytt oinitialiserat objekt. Att returnera denna sopor till högen ber helt enkelt om problem.

    Ta bort dessa rader – eller ännu bättre, använd en initialiseringslista som du gjorde med den första 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) 

    Ett annat problem: I din clear() medlem som du skrev:

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

    Du borde aldrig, aldrig , aldrig kallar direkt till en destruktör som denna. Kompilatorn kan lägga alla möjliga andra koder i destruktorkoden, eftersom den ”vet” att den är den enda som någonsin kommer att kalla den. Till exempel trycker vissa kompilatorer på en flagga för att säga om de ska delete objektet från högen efter att ha gjort förstörelsen. Du har inte gjort det så ovan kan skada alla möjliga saker.

    Flytta istället koden som finns i förstöraren till clear(), och ring sedan helt enkelt clear() från förstöraren.

    En annan, den här plockade upp av @Nick Gammon:

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

    Detta testar om __data -medlemmen är falsk och om capacity och size har definierats. Oroa dig inte: de två sistnämnda har varit … du saknade prefixet __

    [Stoppa också med this-> överallt. Du har använt en __ framför alla dina medlemsvariabler (vilket i sig är emot konventionen ); du behöver inte hamra på att de är medlemsvariabler: kompilatorn vet redan.]

    Kommentarer

    • Jag raderade hela vector(T obj, int count = 1) konstruktören och den kompilerades utan fel, så det var inte ’ t problemet i detta fall, men du har rätt att __data inte initialiserades.
    • should never have compiled – det testade funktionerna snarare än att ringa dem. capacity är en medlemsfunktion, inte en variabel. Jag tog upp det när jag försökte kompilera det under clang.
    • Hee hee! Jag saknade det – och du ’ har rätt!
    • lite råd: Din kod är praktiskt taget oläslig. Använd en bättre kodningsstil : google.github.io/styleguide/cppguide.html
    • Jag ser inte heller varför du gör det. Medan det är fullblåst STL stöds inte, det finns flera bra debugg redigerade implementeringar som du kan hitta med en enkel google-sökning. Om du gör det för att lära dig C ++ är arduino-miljön förmodligen det värsta sättet att göra det på!

Svar

Det är något jag vill lägga till.
Andra har redan påpekat många fel i koden. Och jag kunde peka många fler, men det är inte poängen.
IMO, det största misstaget är att implementera en Vector-klass för Arduino!
Även om det fungerar är det bara en dålig idé. Tänk på att Arduino har mycket begränsat minne. Dynamisk minnesallokering i ett sådant begränsat utrymme låter bara som en dålig idé – du slösar bort minne, du fragmenterar högen och du får nästan ingenting, förutom viss kodningsbekvämlighet.
Jag har ännu inte sett ett fall där man verkligen behöver dynamisk minnestilldelning i ett Arduino-projekt. Och även om du gör det finns det många andra verktyg du kan använda (som stack-allokerade temporära buffertar, arenatilldelare, pooltilldelare och vad som helst). Men igen, förmodligen koden och minneskostnaden för dessa ”generiska” lösningar kommer att vara omöjliga på Arduino.
För det mesta är det bra att använda vektorer, kartor och vad du vill på plattformar som PC (jag jobbar som programmerare för PC-spel, så jag stöter ofta på situationer där tillbehör som dessa är stora ”nej-nej”, men för de flesta applikationer är det bra).
Men på en ”tät” plattform som Arduino, tror jag att du borde stanna ”nära metallen” och vara i total kontroll över vad som händer (minne och CPU-cykler klokt). Helst skulle du bara ha råd med ”luxuri es ”som dessa när du vet vad som händer” under ”och du vet att du kan komma undan med det. Vilket är sällan fallet på ”snäva” plattformar som Arduino.

Kommentarer

  • Du har en poäng här. I synnerhet push_back -idén som stegvis tilldelar mer minne (vilket normalt är bra på en dator med 4 Gb RAM) kommer sannolikt att införa minnesfragmentering. Jag tror att även normala STL-implementeringar fördelar vektorminne (kapacitet i det här fallet) i satser (t.ex. 10 åt gången) för att minska problemet.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *