É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
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. Acapacity
tag függvény, nem változó. Ezt akkor vettem fel, amikor megpróbáltam lefordítani aclang
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.
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.