Erstellen einer Vektorklasse in Arduino

Ich habe Probleme mit diesem Problem, das ich scheinbar umgehen kann. Wenn ich eine Vektorklasse definiere, scheine ich um einige Probleme beim Löschen des zugewiesenen Zeigers zu bekommen.

Seltsamerweise geschieht dies erst, nachdem ich den Speicher „gelesen“ habe. Damit meine ich, dass wenn ich eine Reihe von Werten auf den Vektor zurückschiebe, sie dort zu bleiben scheinen. Sobald ich die Werte gelesen habe, scheint der Datenzeiger in irgendeiner Weise ungültig zu werden, sodass ich beim Versuch, die Zuordnung aufzuheben, abstürze.

Code:

#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 

Ich habe diesen Code in meinem Arduino verwendet und bin mir nicht sicher, ob er hilft.

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

Dieser Code gibt Folgendes aus: Drucken eines neuen VEC! 10 2 Neue VEC drucken! 0 2 3 Neue VEC drucken! 0 2 Neue VEC drucken! 0 (…)

Was könnte dies verursachen? Ich bin jetzt schon eine Weile stonewalled, jeder Einblick, wie man das löst, wird geschätzt. Danke!

Kommentare

  • Am Ende von dem, was Sie Sagen wir, die Code-Ausgaben sind „(…)“ Teil der Ausgabe, die Sie sehen, oder nicht? Wenn es nicht ‚ t ist, wie unterscheidet sich die Ausgabe von dem, was Sie erwartet haben Wenn ein Vektor vier Elemente enthält, sollten vier i.pop_back() -Aufrufe den Vektor leeren.
  • delete this->__data; //program crashes here ... sollte delete[] richtig verwendet werden? Außerdem gibt es in Ihrer Klasse keinen Plattformcode, sodass Sie auf einem PC debuggen können, auf dem viel bessere Debugging-Tools verfügbar sind.

Antwort

Sie hatten viele Probleme mit Ihrem Code, daher ist es schwierig zu wissen, wo Sie anfangen sollen. 🙂

Ich stimme dem zu, was John Burger gesagt hat.

Ich habe Ihren Code auf meinen PC übertragen, um zu vermeiden, dass er jedes Mal hochgeladen wird, und damit ich Valgrind verwenden kann. Sicherlich hat valgrind einen Fehler gemeldet, nachdem Sie den Vektor gedruckt haben. Der Grund dafür ist einfach. Sie haben eine Kopie der Klasse an print_vec übergeben, was bedeutet, dass der Destruktor erhalten hat Wird aufgerufen, wenn print_vec beendet wird, wodurch die Zuweisung des Speichers aufgehoben wird. Sie sollten einen Kopierkonstruktor haben. Ohne sie erstellt der Compiler eine bitweise Kopie der Klasse, was bedeutet, dass Sie jetzt zwei Objekte haben, die denselben zugewiesenen Speicher gemeinsam nutzen.

Eine schnelle und schmutzige Lösung besteht darin, print_vec als Referenz:

void print_vec(vector<int> & v){ 

Damit lauert der Fehler jedoch für die Zukunft.

Ich habe den Kopierkonstruktor in meinem Beispiel implementiert Wenn Sie jedoch unten print_vec als Referenz aufrufen, muss die Klasse kopiert werden, wodurch die Anzahl der new/delete s, die Sie ausführen, verringert wird Speicherfragmentierung.


Wie John Burger sagte: Rufen Sie den Destruktor nicht selbst auf! Sie kann das nicht. Wenn Sie dasselbe im Destruktor und in der Funktion clear tun möchten, lassen Sie den Destruktor einfach clear().


Die Verwendung führender doppelter Unterstriche in Variablen widerspricht dem C ++ – Standard. Tun Sie das nicht. Verwenden Sie einen abschließenden Unterstrich, wenn Sie eine Mitgliedsvariable angeben möchten. Verlieren Sie alle this-> Referenzen. Sie sorgen nur für Unordnung.


Da Sie ein Array zuweisen, sollten Sie delete [] verwenden – das haben Sie an einer Stelle getan, aber nicht an der anderen.


Warum das? Es ist eine unnötige Aufgabe:

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

Führen Sie einfach Folgendes aus:

 vector<int> i; 

Wenn Sie Wenn Sie die Klasse aus irgendeinem Grund zuweisen, sollten Sie auch eine operator= implementieren, da Sie sonst dieselben Probleme haben wie mit dem Kopierkonstruktor (siehe mein Beispiel).


Hier testen Sie Funktionen (Kapazität und Größe), ohne sie aufzurufen:

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

Sie können dies nicht wirklich tun :

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

Wenn Sie den Pfad „else“ verwenden, gibt die Funktion keinen Wert zurück (Sie erhalten eine Compiler-Warnung, wenn Sie Warnungen aktivieren).


Mein überarbeitetes Beispiel mit meinen Vorschlägen. Es wird ohne Warnungen oder Fehler kompiliert und läuft ohne Absturz (auf einem 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(); } } 

Kommentare

  • Okay, vielen Dank! Viele der Probleme waren für mich nur eine Ablenkung. Ich habe versucht, mich auf die Behebung der Speicherprobleme zu konzentrieren, mit denen ich zuerst konfrontiert war.

Antwort

In Ihrem zweiten Konstruktor beginnen Sie mit dem folgenden Code:

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

Das ist tödlich! Sie haben __data nicht initialisiert, sodass es jeden Wert unter der Sonne halten kann. Und es gibt keine Möglichkeit , dies zu tun möglicherweise ein gültiger Zeiger auf vorhandene Daten sein – es ist ein brandneues, nicht initialisiertes Objekt. Das Zurückgeben dieses Müllzeigers auf den Heap erfordert lediglich Probleme.

Entfernen Sie diese Zeilen – oder verwenden Sie noch besser eine Initialisiererliste wie beim ersten Konstruktor:

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) 

Ein weiteres Problem: In Ihr clear() Mitglied, das Sie geschrieben haben:

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

Sie sollten niemals, niemals , ruft niemals einen Destruktor wie diesen direkt auf. Der Compiler kann alle Arten von anderem Code in den Destruktorcode einfügen, da er „weiß“, dass er der einzige ist, der ihn jemals aufruft. Einige Compiler drücken beispielsweise ein Flag vorab, um zu sagen, ob delete das Objekt vom Heap nach der Zerstörung. Sie haben das nicht getan, daher kann das oben Genannte alle möglichen Dinge beschädigen.

Verschieben Sie stattdessen den Code, der sich darin befindet den Destruktor in clear() und rufen Sie dann einfach clear() vom Destruktor aus auf.

Ein anderer, der abgeholt wurde von @Nick Gammon:

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

Hiermit wird getestet, ob das __data -Mitglied falsch ist und ob das capacity und size wurden definiert. Keine Sorge: Die beiden letzteren waren … Sie haben das Präfix __

[Beenden Sie es außerdem überall mit der this->. Sie haben vor allen Ihren Mitgliedsvariablen eine __ verwendet (was selbst gegen die Konvention verstößt) ); Sie müssen nicht nach Hause hämmern, dass es sich um Mitgliedsvariablen handelt: Der Compiler weiß es bereits.]

Kommentare

  • Ich habe den gesamten vector(T obj, int count = 1) -Konstruktor gelöscht und er wurde fehlerfrei kompiliert, sodass ‚ das Problem in diesem Fall nicht war Fall, aber Sie haben Recht, dass __data nicht initialisiert wurde.
  • should never have compiled – es hat die Funktionen getestet, anstatt aufzurufen capacity ist eine Mitgliedsfunktion, keine Variable. Ich habe das aufgegriffen, als ich versuchte, sie unter clang zu kompilieren.
  • Hee hee! Ich habe das verpasst – und Sie ‚ haben Recht!
  • ein kleiner Rat: Ihr Code ist praktisch unlesbar. Verwenden Sie einen besseren Codierungsstil : google.github.io/styleguide/cppguide.html
  • Ich verstehe auch nicht, warum Sie dies tun STL wird nicht unterstützt, es gibt mehrere Well-Debugg ed Implementierungen, die Sie mit einer einfachen Google-Suche finden können. Wenn Sie es tun, um C ++ zu lernen, ist die Arduino-Umgebung wahrscheinlich die schlechteste Methode!

Antwort

Es gibt etwas, das ich hinzufügen möchte.
Andere haben bereits auf zahlreiche Fehler im Code hingewiesen. Und ich könnte noch viel mehr zeigen, aber das ist nicht der Punkt.
IMO, der größte Fehler ist – die Implementierung einer Vektorklasse für das Arduino!
Auch wenn es funktioniert, ist es nur eine schlechte Idee. Denken Sie daran, dass der Arduino SEHR wenig Speicher hat. Die dynamische Speicherzuweisung in einem so beengten Raum klingt einfach nach einer schlechten Idee – Sie verschwenden Speicher, fragmentieren den Heap und gewinnen fast nichts, außer ein wenig Bequemlichkeit beim Codieren.
Ich habe noch keinen Fall gesehen, in dem es wirklich einen gibt benötigt eine dynamische Speicherzuweisung in einem Arduino-Projekt. Und selbst wenn Sie dies tun, gibt es viele andere Tools, die Sie verwenden können (wie stapelzugewiesene temporäre Puffer, Arena-Zuweiser, Pool-Zuweiser und so weiter). Aber auch hier wahrscheinlich die Code- und Speicherkosten von Diese „generischen“ Lösungen sind auf dem Arduino nicht durchführbar.
Meistens ist es in Ordnung, Vektoren, Karten und alles, was Sie möchten, auf Plattformen wie dem PC zu verwenden (ich arbeite als Programmierer für PC-Spiele, daher stoße ich häufig auf Situationen wo Extras wie diese groß sind „no-no“, aber für die meisten Anwendungen ist es in Ordnung).
Aber auf einer „engen“ Plattform wie dem Arduino denke ich, dass Sie „nah am Metall“ bleiben und sein sollten im Idealfall sollten Sie sich nur „Luxus“ leisten es „wie diese, wenn Sie wissen, was“ unter „los ist und Sie wissen, dass Sie damit durchkommen können. Was auf „engen“ Plattformen wie dem Arduino selten der Fall ist.

Kommentare

  • Hier haben Sie einen Punkt. Insbesondere die Idee push_back, die schrittweise mehr Speicher zuweist (was normalerweise auf einem PC mit 4 GB RAM in Ordnung ist), führt wahrscheinlich zu einer Speicherfragmentierung. Ich denke, dass sogar normale STL-Implementierungen Vektorspeicher (in diesem Fall Kapazität) in Stapeln (z. B. jeweils 10) zuweisen, um dieses Problem zu reduzieren.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.