Vektor osztály építése az Arduino-ban

Én küzdök ezzel a problémával, amelyet úgy tűnhetem, hogy megkerülek. Egy vektorosztály meghatározásakor úgy tűnik, hogy hogy a kiosztott mutató törlésével néhány probléma felmerüljön.

Furcsa módon ez csak azután következik be, hogy “elolvastam” a memóriát. Ezt úgy értem, hogy ha egy értéksorozatot tolok vissza a vektorhoz, úgy tűnik, hogy ott maradnak. De miután elolvastam az értékeket, úgy tűnik, hogy az adatmutató valamilyen módon érvénytelenné válik, ezért összeomlik, amikor megpróbálom elosztani.

Kód:

#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 

Ezt a kódot használtam az Arduino-mban, nem biztos benne, hogy segít-e

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: } 

Ez a kód adja ki: Új vec nyomtatása! 10 2 Új vec nyomtatása! 0 2 3 Új vec nyomtatása 0 2 Új vec nyomtatása! 0 (…)

Mi okozhatja ezt? Egy ideje kőfalakon vagyok, bármilyen betekintés a megoldás megoldására értékelhető. Köszönöm!

Hozzászólások

  • A végén mondjuk, hogy a kódkimenetek a megjelenített kimenet „(…)” részét képezik, vagy sem? Ha nem ‘ t, akkor miben tér el a kimenet az elvártól Ha négy elem van egy vektorban, akkor négy i.pop_back() hívásnak ki kell ürítenie a vektort.
  • delete this->__data; //program crashes here ... a következőt kell használnia: delete[] igaz? Ezenkívül nincs platformkód az osztályában, így hibakeresést végezhet a pc-n, ahol sokkal jobb hibakeresési eszközök állnak rendelkezésre.

Válasz

Sok problémád volt a kódoddal, így nehéz megtudni, hogy hol kezdjem. 🙂

Egyetértek azzal, amit John Burger mondott.

Vittem a kódot a számítógépemre, hogy megóvjam a minden egyes feltöltést, és hogy használhassam rajta a valgrind-et is. A valgrind minden bizonnyal hibát jelentett a vektor nyomtatása után. Ennek oka egyszerű. Átadta az osztály másolatát az print_vec címre, ami azt jelentette, hogy a romboló akkor hívják, amikor print_vec kilépett, így elosztotta a memóriát. Szüksége volt egy másolatkészítőre . Enélkül a fordító bitenként másolja az osztályt, ami azt jelenti, hogy most két objektum osztozik ugyanazon a lefoglalt memórián.

Gyors és piszkos javítás az print_vec hivatkozással:

void print_vec(vector<int> & v){ 

Ez azonban a hibát a jövőre leselkedik.

A példában implementáltam a másolás konstruktort Az alábbiakban azonban az print_vec hivatkozással történő meghívása elmenti az átmásolandó osztályt, csökkentve az Ön által végzett new/delete számot, és ezáltal csökkentheti a memória töredezettsége.


Ahogy John Burger mondta: ne hívd magad a rombolót! Te nem tudja ezt megtenni. Ha ugyanazt szeretné megtenni a destruktorban és a clear függvényben, akkor csak a rombolót hívja meg a clear().


A vezető kettős aláhúzás használata a változókban ellentmond a C ++ szabványnak. Ezt ne tegye. Használjon záró aláhúzást, ha tagváltozót szeretne megadni. Veszítse el az összes this-> hivatkozást. Csak elrontják a dolgokat.


Mivel kioszt egy tömböt, a delete [] -et kell használnia – ezt az egyik helyen tette, a másikban nem.


Miért csinálja ezt? Felesleges hozzárendelés:

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

Csak tegye:

 vector<int> i; 

Ha valamilyen oknál fogva hozzárendeli az osztályt, akkor telepítenie kell egy operator= -et is, különben ugyanazok a problémái lesznek, mint a másolat-készítővel. (Lásd a példámat.) >


Itt teszteled a funkciókat (kapacitás és méret) anélkül, hogy meghívnád őket:

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

Ezt nem igazán lehet megtenni :

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

Ha az “else” útvonalat választja, akkor a függvény nem ad vissza értéket (a figyelmeztetések bekapcsolásakor fordító figyelmeztetést kap).


Átdolgozott példám, benne a javaslataimmal. Figyelmeztetések és hibák nélkül áll össze és összeomlás nélkül fut (PC-n):

// 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(); } } 

Megjegyzések

  • Rendben, nagyon köszönöm! Sok kérdés csak a figyelem elterelését jelentette a végén, próbáltam összpontosítani az elején szembesült memóriaproblémák kijavítására.

Válasz

A második konstruktorodban a következő kóddal indulsz:

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

Ez halálos! Nem inicializálta a __data elemet, így bármely értéket a Nap alatt tarthat. És nincs rá mód esetleg legyen érvényes mutató a meglévő adatokra – ez egy vadonatúj, inicializálatlan objektum. Ha visszatesszük ezt a szemétmutatót a kupacba, egyszerűen gondot okoz.

Távolítsa el ezeket a sorokat – vagy még jobb, használja az inicializáló listát, mint az első konstruktornál:

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) 

Egy másik probléma: clear() tagod, akit írtál:

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

Soha, soha , soha közvetlenül nem hívhat ilyen destruktort. A fordító mindenféle más kódot beilleszthet a destruktorkódba, mivel “tudja”, hogy csak ez hívja valaha. Például egyes fordítók előre nyomnak egy zászlót, hogy megmondják, hogy delete az objektum a kupacból a megsemmisítés után. Ezt még nem tetted meg, így a fentiek mindenfélét megronthatnak.

Ehelyett áthelyezd a kódot, a destruktort clear() -be, majd egyszerűen hívja meg a clear() -t a destruktorból.

Egy másik, ez vette fel @Nick Gammon:

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

Ez azt teszteli, hogy a __data tag hamis-e, és hogy a capacity és a size függvényeket meghatároztuk. Ne aggódjon: az utóbbi kettő már … hiányzott a __ előtag …

[Ezenkívül állítsa le mindenhol a this-> paranccsal. Minden tagváltozója előtt használt egy __ -t (ami maga is ellentmond a konvenciónak) ); nem kell hazatérnie, hogy tagváltozók: a fordító már tudja.]

Megjegyzések

  • Töröltem a teljes vector(T obj, int count = 1) konstruktort, és hibátlanul fordítottam össze, így nem volt ebben a probléma ‘ esetben, de igazad van abban, hogy a __data nem lett inicializálva.
  • should never have compiled – a funkciókat tesztelte ahelyett, hogy hívta volna őket. A capacity tag függvény, nem változó. Ezt akkor vettem fel, amikor megpróbáltam lefordítani a clang alatt.
  • Hee hee! Ezt hiányoltam – és ‘ igazad van!
  • egy kis tanács: A kódod gyakorlatilag nem olvasható. Használj jobb kódolási stílust : google.github.io/styleguide/cppguide.html
  • Azt sem értem, miért csinálod ezt. Bár teljes értékű Az STL nem támogatott, számos jól hibakereső van olyan megvalósításokat, amelyeket egy egyszerű google kereséssel találhat meg. Ha a C ++ elsajátítása érdekében csinálja, valószínűleg az arduino környezet a legrosszabb módja ennek!

Válasz

Van valami, amit szeretnék hozzáfűzni.
Mások már számos hibára rámutattak a kódban. És még sokat tudnék mutatni, de nem ez a lényeg.
IMO, a legnagyobb hiba az – egy Vector osztály bevezetése az Arduino számára!
Még ha működik is, ez csak egy rossz ötlet. Ne feledje, hogy az Arduino NAGYON korlátozott memóriával rendelkezik. A dinamikus memória-allokáció egy ilyen korlátozott térben csak rossz ötletnek tűnik – pazarolja a memóriát, széttöredezi a kupacot, és szinte semmit sem nyer, kivéve némi kódolási kényelmet.
Még nem láttam olyan esetet, amikor valaki valóban dinamikus memória-allokációra van szüksége egy Arduino projektben. És még akkor is, ha igen, sok más eszközt is használhat (például verem-allokált temp pufferek, aréna-allokátorok, pool-allokátorok és mi más). De megint valószínűleg a kód és a memória költsége ezek a “generikus” megoldások kivitelezhetetlenek lesznek az Arduino-n.
A legtöbb esetben vektorokat, térképeket és bármit tetszik használni olyan platformokon, mint a PC (PC-s játékprogramozóként dolgozom, ezért gyakran találkozom helyzetekkel) ahol az ilyen extrák nagyok “nem-nem” -ek, de a legtöbb alkalmazáshoz ez rendben van. a folyamat teljes ellenőrzésében (a memória és a CPU ciklusai bölcsen). Ideális esetben csak a “luxurit” engedheti meg magának Ezek “ilyenek, amikor tudod, mi folyik” alatta “, és tudod, hogy megúszhatod. Ez ritkán fordul elő olyan “szűk” platformokon, mint az Arduino.

Megjegyzések

  • Van itt valami pontod. Különösen a push_back ötlet, amely inkrementálisan több memóriát oszt ki (ez általában rendben van egy 4 Gb RAM-mal rendelkező számítógépen), valószínűleg a memória töredezettségét idézi elő. Azt hiszem, még a normális STL megvalósítások is elosztják a vektormemóriát (ebben az esetben a kapacitást) kötegekben (pl. Egyszerre 10-et) a probléma csökkentése érdekében.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük