Vytváření třídy Vector v Arduinu

Bojuji s tímto problémem, který se mi zdá obejít. Při definování třídy vektorů se mi zdá získat nějaké problémy při mazání přiděleného ukazatele.

Kupodivu se to děje až poté, co jsem „přečetl“ paměť. Tím mám na mysli to, že když posunu řadu hodnot zpět na vektor, zdá se, že tam zůstanou. Ale jakmile jsem přečetl hodnoty, zdá se, že ukazatel dat je nějakého druhu neplatný, takže při pokusu o jeho uvolnění dojde k chybě.

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 

Používal jsem tento kód v mém Arduinu, nejsem si jistý, jestli to pomůž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: } 

Výstup tohoto kódu: Tisk nové věci! 10 2 Tisk nových věcí! 0 2 3 Tisk nových věcí! 0 2 Tisk nových věcí! 0 (…)

Co to může způsobovat? Na chvíli jsem kamenný, jakýkoli pohled na to, jak to vyřešit, se cení. Díky!

Komentáře

  • Na konci toho, co jste řekněme, že výstup kódu je „(…)“ součástí výstupu, který vidíte, nebo ne? Pokud to není ‚ t, jak se výstup liší od toho, co jste očekávali vidět? Pokud jsou ve vektoru čtyři položky, měly by čtyři i.pop_back() volání vektor vyprázdnit.
  • delete this->__data; //program crashes here ... měli byste používat delete[] správně? Ve vaší třídě také není žádný kód platformy, takže byste mohli ladit na PC, kde jsou k dispozici mnohem lepší ladicí nástroje.

Odpověď

S kódem jste měli spoustu problémů, takže je těžké vědět, kde začít. 🙂

Souhlasím s tím, co řekl John Burger.

Vzal jsem si váš kód do počítače, abych pokaždé ušetřil nahromadění jeho nahrávání, a také abych na něm mohl použít valgrind. Valgrind jistě nahlásil chybu po vytištění vektoru. Důvod je jednoduchý. Předali jste kopii třídy do print_vec, což znamenalo, že destruktor dostal zavolal při print_vec ukončení, čímž uvolnil paměť. Měli jste mít kopírovací konstruktor . Bez něj kompilátor vytvoří bitovou kopii třídy, což znamená, že nyní máte dva objekty sdílející stejnou přidělenou paměť.

Rychlou a špinavou opravou je zavolat print_vec podle odkazu:

void print_vec(vector<int> & v){ 

To však ponechává chybu číhající na budoucnost.

V mém příkladu jsem implementoval konstruktor kopírování níže však volání print_vec odkazem uloží třídu, kterou je třeba zkopírovat, čímž se sníží počet new/delete s, které děláte, čímž se případně sníží fragmentace paměti.


Jak řekl John Burger: nevolajte destruktor sami! vy nemůžu to udělat. Pokud chcete dělat totéž v destruktoru a ve funkci clear, stačí, aby destruktor zavolal clear().


Použití úvodních dvojitých podtržítků v proměnných je v rozporu se standardem C ++. Nedělejte to. Pokud chcete označit členskou proměnnou, použijte koncové podtržítko. Ztratit všechny this-> odkazy. Prostě to přeplňují.


Protože přidělujete pole, měli byste použít delete [] – udělali jste to na jednom místě, ale ne na druhém.


Proč to dělat? Je to zbytečné přiřazení:

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

Stačí:

 vector<int> i; 

Pokud hodláte třídu z nějakého důvodu přiřadit, měli byste také implementovat operator=, jinak budete mít stejné problémy, jaké jste měli s konstruktorem kopie. (Viz můj příklad).


Zde testujete funkce (kapacitu a velikost), aniž byste je volali:

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

Toto opravdu nemůžete udělat :

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

Pokud zvolíte cestu „else“, funkce nevrací žádnou hodnotu (při zapnutí varování se zobrazí upozornění kompilátoru).


Můj přepracovaný příklad s mými návrhy. Kompiluje se bez varování nebo chyb a běží bez havárie (na 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(); } } 

Komentáře

  • Dobře, moc děkuji! Mnoho problémů bylo na mém konci pouhým rozptýlením, snažil jsem se zaměřit na vyřešení problémů s pamětí, kterým jsem zpočátku čelil.

Odpovědět

Ve svém druhém konstruktoru začnete s následujícím kódem:

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

To je smrtící! Nejste inicializováni __data, takže by mohla držet jakoukoli hodnotu pod Sluncem. A neexistuje , že by mohla možná může být platným ukazatelem na existující data – je to zbrusu nový neinicializovaný objekt. Vrácení tohoto ukazatele odpadu na haldu jednoduše vyžaduje potíže.

Odeberte tyto řádky – nebo ještě lépe, použijte seznam inicializátorů jako u prvního konstruktoru:

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) 

Další problém: V váš clear() člen, kterého jste napsali:

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

Nikdy, nikdy , nikdy přímo nevolajte takový destruktor. Kompilátor může do kódu destruktoru vložit nejrůznější další kódy, protože „ví“, že je to jediný, kdo jej kdy zavolá. Například některé kompilátory předem stisknou příznak, aby řekly, zda delete objekt z haldy po provedení zničení. To jste neudělali, takže výše uvedené může poškodit vše možné.

Místo toho přesuňte kód, který je v destruktor do clear() a potom jednoduše zavolejte clear() z destruktoru.

Další, tento zvedl autor @Nick Gammon:

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

Toto testuje, zda je člen __data nepravdivý a zda je capacity a size. Nedělejte si starosti: poslední dva byli … zmeškali jste předponu __

[Také to zastavte s this-> všude. Použili jste __ před všemi svými členskými proměnnými (což je samo o sobě proti konvenci ); nemusíte kladit domů skutečnost, že se jedná o členské proměnné: překladač již ví.]

Komentáře

  • Odstranil jsem celý vector(T obj, int count = 1) konstruktor a ten se zkompiloval bez chyb, takže to nebyl ‚ problém v tomto případ, ale máte pravdu, že __data nebyl inicializován.
  • should never have compiled – testoval funkce spíše než volání je. capacity je členská funkce, nikoli proměnná. Vybral jsem to, když jsem to zkusil kompilovat pod clang.
  • Hee hee! To mi chybělo – a máte ‚ pravdu!
  • malá rada: Váš kód je prakticky nečitelný. Použijte lepší styl kódování : google.github.io/styleguide/cppguide.html
  • Také nechápu, proč to děláte. STL není podporována, existuje několik dobře laditelných programů ed implementace, které najdete pomocí jednoduchého vyhledávání google. Pokud to děláte, abyste se naučili C ++, prostředí arduino je pravděpodobně nejhorší způsob, jak to udělat!

Odpověď

Je tu něco, co bych chtěl přidat.
Ostatní již poukázali na řadu chyb v kódu. Mohl bych ukázat mnohem více, ale o to nejde.
IMO, největší chybou je – implementace třídy Vector pro Arduino!
I když to funguje, je to jen špatný nápad. Mějte na paměti, že Arduino má VELMI omezené množství paměti. Dynamické přidělování paměti v tak omezeném prostoru zní jako špatný nápad – plýtváte pamětí, fragmentujete hromadu a nezískáváte téměř nic, kromě nějaké výhody kódování.
Ještě jsem neviděl případ, kdy by někdo opravdu potřebuje dynamické přidělení paměti v projektu Arduino. A i když to uděláte, můžete použít mnoho dalších nástrojů (například dočasné vyrovnávací paměti přidělené zásobníku, alokátory arén, alokátory bazénů a co ne). Ale opět, pravděpodobně náklady na kód a paměť tato „obecná“ řešení budou na Arduinu neproveditelná.
Většinou je v pořádku používat vektory, mapy a cokoli chcete na platformách jako PC (pracuji jako programátor počítačových her, takže se často setkávám se situacemi kde doplňky jako tyto jsou velké „ne-ne“, ale pro většinu aplikací je to v pořádku).
Ale na „těsné“ platformě, jako je Arduino, si myslím, že byste měli zůstat „blízko kovu“ a být máte úplnou kontrolu nad tím, co se děje (paměťové a CPU cykly). V ideálním případě byste si měli dovolit pouze „luxuri“ es „jako tito, když víte, co se děje“ pod „a víte, že se z toho můžete dostat. Což je zřídka případem „těsných“ platforem, jako je Arduino.

Komentáře

  • Zde máte bod. Zejména myšlenka push_back, která postupně přiděluje více paměti (což je na PC se 4 GB RAM obvykle v pořádku) pravděpodobně zavede fragmentaci paměti. Myslím, že i normální implementace STL přidělují vektorovou paměť (v tomto případě kapacitu) v dávkách (např. 10 najednou), aby se tento problém snížil.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *