Construirea unei clase Vector în Arduino

Mă lupt cu această problemă pe care par să o rezolv. Când definesc o clasă vectorială, par pentru a obține unele probleme la ștergerea indicatorului alocat.

În mod ciudat, acest lucru se întâmplă numai după ce am „citit” memoria. Ceea ce vreau să spun prin aceasta este că, dacă împing înapoi o serie de valori la vector, acestea par să rămână acolo. Dar, odată ce am citit valorile, indicatorul de date pare să devină nevalid, așa că am prăbușit când încerc să-l aloc.

Cod:

#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 

Foloseam acest cod în Arduino, nu sunt sigur dacă ajută

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

Acest cod generează: Tipărire vec nouă! 10 2 Tipărire vec nouă! 0 2 3 Tipărire vec nouă! 0 2 Tipărire vec nouă! 0 (…)

Ce ar putea fi cauza asta? M-am ferit de ceva vreme acum, orice apreciere despre cum să rezolv acest lucru este apreciată. Mulțumesc!

Comentarii

  • La sfârșitul a ceea ce spuneți ieșirile de cod, este „(…)” o parte din ieșirea pe care o vedeți sau nu? Dacă nu este ‘ t, în ce fel diferă ieșirea de ceea ce vă așteptați pentru a vedea? Dacă există patru elemente într-un vector, patru apeluri i.pop_back() ar trebui să golească vectorul.
  • delete this->__data; //program crashes here ... ar trebui să utilizați delete[] nu? De asemenea, nu există un cod de platformă în clasa dvs., astfel încât să puteți depana pe computer, unde există instrumente de depanare mult mai bune disponibile.

Răspuns

Ați avut o mulțime de probleme cu codul dvs., deci este greu să știți de unde să începeți. 🙂

Sunt de acord cu ceea ce a spus John Burger.

Am luat codul tău pe computerul meu pentru a economisi înțelegerea încărcându-l de fiecare dată și, de asemenea, pentru a putea folosi valgrind pe el. Cu siguranță, valgrind a raportat o eroare după ce ați imprimat vectorul. Motivul pentru aceasta este simplu. Ați trecut o copie a clasei către print_vec ceea ce însemna că distructorul a primit apelat la ieșirea print_vec, astfel repartizând memoria. Ar fi trebuit să aveți un constructor de copii . Fără acesta, compilatorul face o copie bit în clasă a clasei, ceea ce înseamnă că acum aveți două obiecte care împart aceeași memorie alocată.

O soluție rapidă și murdară este să apelați print_vec prin referință:

void print_vec(vector<int> & v){ 

Cu toate acestea, acest lucru lasă bug-ul ascuns în viitor.

Am implementat constructorul copiei în exemplul meu mai jos, totuși apelarea print_vec prin referință salvează clasa care trebuie copiată, reducând numărul de new/delete pe care le faceți, reducând astfel posibil fragmentarea memoriei.


Așa cum a spus John Burger: nu apelați singur distructorul! nu se poate face asta. Dacă doriți să faceți același lucru în distructor și în funcția clear, doar obțineți distructorul să apeleze clear().


Folosirea punctelor de subliniere duble în variabile este contrară standardului C ++. Nu faceți asta. Utilizați un subliniat final dacă doriți să indicați o variabilă membru. Pierdeți toate referințele this->. Pur și simplu aglomerează lucrurile.


Deoarece alocați o matrice, ar trebui să utilizați delete [] – ați făcut asta într-un singur loc, dar nu în celălalt.


De ce faceți acest lucru? Este o sarcină inutilă:

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

Doar faceți:

 vector<int> i; 

Dacă urmează să atribuiți clasa dintr-un anumit motiv, ar trebui să implementați și un operator= sau veți avea aceleași probleme pe care le-ați avut cu constructorul copiei. (Vedeți exemplul meu).


Aici testați funcțiile (capacitatea și dimensiunea) fără a le apela:

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

Nu puteți face asta cu adevărat :

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

Dacă luați calea „else”, atunci funcția nu returnează nicio valoare (primiți un avertisment al compilatorului dacă activați avertismentele).


Exemplul meu refăcut, cu sugestiile mele în el. Se compilează fără avertismente sau erori și rulează fără blocare (pe un computer):

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

Comentarii

  • Bine, mulțumesc mult! Multe dintre problemele mele erau doar o simplă distragere a atenției, încercam să mă concentrez pe rezolvarea problemelor de memorie cu care mă confruntam la început.

Răspuns

În al doilea constructor începeți cu următorul cod:

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

Acest lucru este mortal! „Nu ați inițializat __data, deci ar putea conține orice valoare sub Soare. Și nu există nici o cale de eventual să fie un indicator valid la datele existente – este „un obiect nou-inițial nou-nouț. Întoarcerea acestui indicator de gunoi în grămadă presupune pur și simplu probleme.

Eliminați acele linii – sau chiar mai bine, utilizați o listă de inițializatoare așa cum ați făcut cu primul constructor:

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) 

O altă problemă: în clear() membru pe care l-ați scris:

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

Nu ar trebui niciodată, niciodată , niciodată nu apelează direct un astfel de distructor. Compilatorul poate introduce tot felul de alte coduri în codul distructor, deoarece „știe” că este singurul care îl va apela vreodată. De exemplu, unii compilatori pre-împing un flag pentru a spune dacă să delete obiectul din heap după ce ați făcut distrugerea. Nu ați făcut asta, astfel încât cele de mai sus pot corupe tot felul de lucruri.

În schimb, mutați codul care se află în distructorul în clear(), apoi apelați pur și simplu clear() de la distructor.

Altul, acesta a luat de @Nick Gammon:

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

Aceasta testează dacă membrul __data este fals și dacă capacity și size au fost definite. Nu vă faceți griji: ultimele două au fost … ați ratat prefixul __

[De asemenea, opriți-l cu this-> peste tot. Ați folosit un __ înainte de toate variabilele membre (ceea ce este el însuși împotriva convenției) ); nu trebuie să știți că sunt variabile membre: compilatorul știe deja.]

Comentarii

  • Am șters întregul constructor vector(T obj, int count = 1) și a fost compilat fără erori, astfel încât nu ‘ a fost problema din acest caz, dar aveți dreptate că __data nu a fost inițializat.
  • should never have compiled – a testat funcțiile, mai degrabă decât apelarea them. capacity este o funcție de membru, nu o variabilă. Am preluat asta când am încercat să o compilez sub clang.
  • Hee hee! Mi-a fost dor – și ai ‘ dreptate!
  • un sfat: codul tău este practic ilizibil. Folosește un stil de codare mai bun : google.github.io/styleguide/cppguide.html
  • De asemenea, nu reușesc să văd de ce faceți acest lucru. STL nu este acceptat, există mai multe depanări bine implementări ed pe care le puteți găsi cu o simplă căutare pe Google. Dacă faceți acest lucru pentru a învăța C ++, mediul arduino este probabil cel mai prost mod de a face acest lucru!

Răspuns

Există ceva ce aș vrea să adaug.
Alții au subliniat deja numeroase erori în cod. Și aș putea indica multe altele, dar nu asta este ideea.
IMO, cea mai mare greșeală este – implementarea unei clase Vector pentru Arduino!
Chiar dacă funcționează, este doar o idee proastă. Rețineți că Arduino are o cantitate FOARTE limitată de memorie. Alocarea dinamică a memoriei într-un spațiu atât de restrâns sună doar ca o idee proastă – risipiți memoria, fragmentați grămada și nu câștigați aproape nimic, cu excepția unor comodități de codificare.
încă nu am văzut un caz în care are nevoie de alocare dinamică a memoriei într-un proiect Arduino. Și chiar dacă o faceți, există multe alte instrumente pe care le puteți utiliza (cum ar fi tampoane temporare alocate în stivă, alocatori de arena, alocatori de pool și ce nu). Dar, din nou, probabil codul și costul memoriei aceste soluții „generice” vor fi irealizabile pe Arduino.
De cele mai multe ori este bine să folosești vectori, hărți și orice îți place pe platforme precum PC (lucrez ca programator de jocuri pe PC, așa că întâlnesc deseori situații unde extras ca acestea sunt mari „nu-nu”, dar pentru majoritatea aplicațiilor este bine).
Dar, pe o platformă „strânsă” precum Arduino, cred că ar trebui să rămâi „aproape de metal” și să fii în control total asupra a ceea ce se întâmplă (memoria și ciclurile procesorului înțelept). În mod ideal, ar trebui să vă permiteți doar „luxuri” este „ca acestea când știi ce se întâmplă„ dedesubt ”și știi că poți scăpa de ea. Ceea ce se întâmplă rar pe platformele „strânse”, cum ar fi Arduino.

Comentarii

  • Ai un punct aici. În special, ideea push_back care alocă în mod incremental mai multă memorie (care este în mod normal bine pe un PC cu 4 Gb de RAM) este probabil să introducă fragmentarea memoriei. Cred că chiar și implementările STL normale alocă memorie vectorială (capacitate în acest caz) în loturi (de exemplu, 10 la un moment dat) pentru a reduce această problemă.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *