Creazione di una classe Vector in Arduino

Sto lottando con questo problema che mi sembra di aggirare. Quando definisco una classe vettoriale, mi sembra per ottenere alcuni problemi durante leliminazione del puntatore allocato.

Stranamente, questo accade solo dopo aver “letto” la memoria. Ciò che intendo con questo è che se rimetto una serie di valori al vettore, sembrano rimanere lì. Ma una volta che ho letto i valori, il puntatore ai dati sembra non essere più valido di qualche tipo, quindi vado in crash quando provo a deallocarlo.

Codice:

#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 

Stavo usando questo codice nel mio Arduino, non sono sicuro che sia daiuto

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

Questo codice genera: Stampa nuovo vec! 10 2 Stampa nuovo vec! 0 2 3 Stampa nuovo vec! 0 2 Stampa nuovo vec! 0 (…)

Cosa potrebbe causare questo? Sono ostacolato da un po di tempo, ogni intuizione su come risolverlo è apprezzata. Grazie!

Commenti

  • Alla fine di quello che hai dì loutput del codice, “(…)” fa parte delloutput che vedi o no? Se non è ‘ t, in che modo loutput differisce da quello che ti aspettavi da vedere? Se ci sono quattro elementi in un vettore, quattro i.pop_back() chiamate dovrebbero svuotare il vettore.
  • delete this->__data; //program crashes here ... dovresti usare delete[] giusto? Inoltre non cè codice piattaforma nella tua classe, quindi puoi eseguire il debug su PC dove sono disponibili strumenti di debug molto migliori.

Risposta

Hai avuto molti problemi con il tuo codice, quindi è difficile sapere da dove cominciare. 🙂

Sono daccordo con quello che ha detto John Burger.

Ho portato il tuo codice sul mio PC per evitare di doverlo caricare ogni volta, e anche così ho potuto usare valgrind su di esso. Sicuramente valgrind ha segnalato un errore dopo aver stampato il vettore. Il motivo è semplice. Hai passato una copia della classe a print_vec, il che significa che il distruttore ha ottenuto chiamato quando print_vec è terminato, deallocando così la memoria. Avresti dovuto avere un costruttore di copie . Senza di essa il compilatore crea una copia bit per bit della classe, il che significa che ora hai due oggetti che condividono la stessa memoria allocata.

Una soluzione rapida e sporca è chiamare print_vec per riferimento:

void print_vec(vector<int> & v){ 

Tuttavia questo lascia il bug in agguato per il futuro.

Ho implementato il costruttore di copia nel mio esempio sotto, tuttavia chiamando print_vec per riferimento si evita che la classe debba essere copiata, riducendo il numero di new/delete che si sta facendo, riducendo eventualmente frammentazione della memoria.


Come ha detto John Burger: non chiamare tu stesso il distruttore! Tu non posso farlo. Se vuoi fare la stessa cosa nel distruttore e nella funzione clear, fai in modo che il distruttore chiami clear().


Luso di doppi trattini bassi iniziali nelle variabili è contrario allo standard C ++. Non farlo. Utilizzare un trattino basso finale se si desidera indicare una variabile membro. Perdi tutti i riferimenti this->. Semplicemente ingombrano le cose.


Dato che allochi un array dovresti usare delete [] – lhai fatto in un posto ma non nellaltro.


Perché farlo? È un compito non necessario:

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

Basta fare:

 vector<int> i; 

Se tu stai per assegnare la classe per qualche motivo dovresti anche implementare un operator= o avrai gli stessi problemi che hai avuto con il costruttore di copia. (Vedi il mio esempio).


Qui stai testando le funzioni (capacità e dimensioni) senza chiamarle:

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

Non puoi “t farlo davvero :

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

Se prendi il percorso “else”, la funzione non restituisce alcun valore (ricevi un avviso del compilatore se attivi gli avvisi).


Il mio esempio rielaborato, con i miei suggerimenti in esso. Si compila senza avvisi o errori e funziona senza crash (su un 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(); } } 

Commenti

  • Va bene, grazie mille! Molti dei problemi erano solo una semplice distrazione da parte mia, stavo cercando di concentrarmi sulla risoluzione dei problemi di memoria che stavo affrontando allinizio.

Risposta

Nel tuo secondo costruttore inizi con il seguente codice:

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

Questo è mortale! Non hai inizializzato __data, quindi potrebbe contenere qualsiasi valore sotto il Sole. E non è possibile che possa possibilmente essere un valido puntatore a dati esistenti – è “un oggetto nuovo di zecca non inizializzato. Restituire questo puntatore spazzatura allheap è semplicemente chiedere problemi.

Rimuovi quelle righe o, ancora meglio, usa un elenco di inizializzatori come hai fatto con il primo costruttore:

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) 

Un altro problema: in il tuo clear() membro che hai scritto:

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

Non dovresti mai, mai , mai chiamare direttamente un distruttore come questo. Il compilatore può inserire ogni sorta di altro codice nel codice del distruttore, poiché “sa” che è lunico che lo chiamerà mai. Ad esempio, alcuni compilatori premono un flag per dire se delete loggetto dallheap dopo aver eseguito la distruzione. Non lhai fatto, quindi quanto sopra può danneggiare ogni sorta di cose.

Invece, sposta il codice che si trova in il distruttore in clear(), quindi chiama semplicemente clear() dal distruttore.

Un altro, questo rilevato di @Nick Gammon:

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

Verifica se il membro __data è falso e se il capacity e size sono state definite. Non preoccuparti: gli ultimi due sono stati … ti sei perso il prefisso __

[Inoltre, interrompila con this-> ovunque. Hai “usato un __ prima di tutte le tue variabili membro (che è di per sé contro le convenzioni ); non è necessario martellare a casa il fatto che sono variabili membro: il compilatore lo sa già.]

Commenti

  • Ho eliminato lintero vector(T obj, int count = 1) costruttore ed è stato compilato senza errori, quindi ‘ il problema in questo caso, ma hai ragione che __data non è stato inizializzato.
  • should never have compiled – ha testato le funzioni invece di chiamare loro. capacity è una funzione membro, non una variabile. Lho capito quando ho provato a compilarlo in clang.
  • Ih ih! Mi mancava e tu ‘ hai ragione!
  • un piccolo consiglio: il tuo codice è praticamente illeggibile. Usa uno stile di codifica migliore : google.github.io/styleguide/cppguide.html
  • Inoltre non riesco a capire perché lo stai facendo. Anche se in piena regola STL non è supportato, ci sono diversi well-debugg ed implementazioni che puoi trovare con una semplice ricerca su google. Se lo stai facendo per imparare il C ++, lambiente arduino è probabilmente il modo peggiore per farlo!

Answer

Cè qualcosa che vorrei aggiungere.
Altri hanno già segnalato numerosi errori nel codice. E potrei indicarne molti di più, ma non è questo il punto.
IMO, lerrore più grande è: implementare una classe Vector per Arduino!
Anche se funziona, è solo una cattiva idea. Tieni presente che Arduino ha una quantità di memoria MOLTO limitata. Lallocazione dinamica della memoria in uno spazio così limitato suona come una cattiva idea: stai sprecando memoria, “stai frammentando lheap e non ottieni quasi nulla, tranne un po di comodità di codifica.
Devo ancora vedere un caso in cui uno necessita di allocazione dinamica della memoria in un progetto Arduino. E anche se lo fai, ci sono molti altri strumenti che puoi usare (come buffer temporanei allocati nello stack, allocatori di arena, allocatori di pool e quantaltro). Ma ancora una volta, probabilmente il costo del codice e della memoria di queste soluzioni “generiche” saranno irrealizzabili su Arduino.
La maggior parte delle volte va bene usare vettori, mappe e qualsiasi cosa ti piaccia su piattaforme come il PC (lavoro come programmatore di giochi per PC, quindi spesso incontro situazioni dove extra come questi sono grandi “no-no”, ma per la maggior parte delle applicazioni va bene).
Ma, su una piattaforma “stretta” come Arduino, penso che dovresti stare “vicino al metallo” ed essere nel controllo totale di ciò che sta accadendo (memoria e cicli di CPU saggio). Idealmente, dovresti permetterti solo “luxuri es “come questi quando sai cosa sta succedendo” sotto “e sai che puoi farla franca. Il che è raramente il caso su piattaforme “strette” come Arduino.

Commenti

  • Hai un punto qui. In particolare lidea push_back che alloca in modo incrementale più memoria (che normalmente va bene su un PC con 4 Gb di RAM) è probabile che introduca la frammentazione della memoria. Penso che anche le normali implementazioni STL allocino la memoria vettoriale (capacità in questo caso) in batch (ad es. 10 alla volta) per ridurre questo problema.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *