Construire une classe Vector dans Arduino

Je me bats avec ce problème dont je peux sembler contourner. Lors de la définition dune classe vectorielle, il me semble pour obtenir des problèmes lors de la suppression du pointeur alloué.

Curieusement, cela ne se produit quaprès avoir « lu » la mémoire. Ce que je veux dire par là, cest que si je repousse une série de valeurs vers le vecteur, elles semblent y rester. Mais une fois que jai lu les valeurs, le pointeur de données semble devenir invalide, donc je plante en essayant de le désallouer.

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 

Jutilisais ce code dans mon Arduino, je ne sais pas si cela aide

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

Ce code génère: Impression dun nouveau vec! 10 2 Impression dun nouveau vec! 0 2 3 Impression dun nouveau vec! 0 2 Impression dun nouveau vec! 0 (…)

Quest-ce qui pourrait en être la cause? Je « suis bloqué depuis un moment maintenant, tout aperçu sur la façon de résoudre ce problème est apprécié. Merci!

Commentaires

  • À la fin de ce que vous dites les sorties de code, est-ce que «(…)» fait partie de la sortie que vous voyez, ou non? Si ce nest pas ‘ t, en quoi la sortie diffère-t-elle de ce que vous attendiez à voir? Sil y a quatre éléments dans un vecteur, quatre appels i.pop_back() doivent vider le vecteur.
  • delete this->__data; //program crashes here ... devrait utiliser delete[], nest-ce pas? De plus, il ny a pas de code de plate-forme dans votre classe, vous pouvez donc déboguer sur PC où il existe de bien meilleurs outils de débogage disponibles.

Réponse

Vous avez eu beaucoup de problèmes avec votre code, il est donc difficile de savoir par où commencer. 🙂

Je suis daccord avec ce que John Burger a dit.

Jai pris votre code sur mon PC pour éviter de le télécharger à chaque fois, et aussi pour pouvoir utiliser valgrind dessus. Certes, valgrind a signalé une erreur après avoir imprimé le vecteur. La raison en est simple. Vous avez transmis une copie de la classe à print_vec, ce qui signifie que le destructeur a obtenu appelé quand print_vec est sorti, libérant ainsi la mémoire. Vous auriez dû avoir un constructeur de copie . Sans cela, le compilateur fait une copie au niveau du bit de la classe, ce qui signifie que vous avez maintenant deux objets partageant la même mémoire allouée.

Une solution rapide et sale consiste à appeler print_vec par référence:

void print_vec(vector<int> & v){ 

Cependant, cela laisse le bogue traîner dans le futur.

Jai implémenté le constructeur de copie dans mon exemple ci-dessous, cependant appeler print_vec par référence évite à la classe davoir à être copiée, réduisant le nombre de new/delete s que vous faites, réduisant ainsi éventuellement fragmentation de la mémoire.


Comme John Burger la dit: nappelez pas le destructeur vous-même! Vous je ne peux pas faire ça. Si vous voulez faire la même chose dans le destructeur et la fonction clear, demandez simplement au destructeur dappeler clear().


Lutilisation de doubles traits de soulignement dans les variables est contraire à la norme C ++. Ne faites pas cela. Utilisez un trait de soulignement à la fin si vous souhaitez indiquer une variable membre. Perdez toutes les références this->. Ils ne font quencombrer les choses.


Puisque vous allouez un tableau, vous devez utiliser delete [] – vous lavez fait à un endroit mais pas à lautre.


Pourquoi faire ça? Cest une affectation inutile:

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

Faites simplement:

 vector<int> i; 

Si vous allez assigner la classe pour une raison quelconque, vous devriez également implémenter un operator= ou vous aurez les mêmes problèmes que vous avez eu avec le constructeur de copie. (Voir mon exemple).


Ici, vous testez des fonctions (capacité et taille) sans les appeler:

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

Vous ne pouvez pas vraiment faire ça :

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

Si vous prenez le chemin « else », la fonction ne renvoie aucune valeur (vous obtenez un avertissement du compilateur si vous activez les avertissements).


Mon exemple retravaillé, avec mes suggestions dedans. Il se compile sans avertissements ni erreurs et sexécute sans planter (sur 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(); } } 

Commentaires

  • Très bien, merci beaucoup! Beaucoup de problèmes étaient simplement une distraction de mon côté, jessayais de me concentrer sur la résolution des problèmes de mémoire auxquels jétais confronté au début.

Réponse

Dans votre deuxième constructeur, vous commencez avec le code suivant:

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

Cest mortel! Vous « navez pas initialisé __data, il pourrait donc contenir nimporte quelle valeur sous le soleil. Et il ny a aucun moyen que cela puisse éventuellement être un pointeur valide vers des données existantes – cest un tout nouvel objet non initialisé. Le retour de ce pointeur dordures dans le tas demande simplement des problèmes.

Supprimez ces lignes – ou mieux encore, utilisez une liste dinitialisation comme vous lavez fait avec le premier constructeur:

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 autre problème: In votre clear() membre que vous avez écrit:

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

Vous ne devriez jamais, jamais , jamais appeler directement un destructeur comme celui-ci. Le compilateur peut mettre toutes sortes dautres codes dans le code du destructeur, car il « sait » que cest le seul qui lappellera jamais. Par exemple, certains compilateurs pré-poussent un indicateur pour indiquer sil faut delete lobjet du tas après avoir effectué la destruction. Vous navez pas fait cela, donc ce qui précède peut corrompre toutes sortes de choses.

Au lieu de cela, déplacez le code qui se trouve dans le destructeur dans clear(), puis appelez simplement clear() à partir du destructeur.

Un autre, celui-ci a ramassé par @Nick Gammon:

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

Ceci teste si le membre __data est faux et si le capacity et size ont été définies. Ne vous inquiétez pas: les deux derniers ont été … vous avez manqué le préfixe __

[Aussi, arrêtez-le avec le this-> partout. Vous « avez utilisé un __ avant toutes vos variables membres (ce qui est lui-même contraire aux conventions ); vous navez pas besoin de marteler le fait quil sagit de variables membres: le compilateur le sait déjà.]

Commentaires

  • Jai supprimé lintégralité du constructeur vector(T obj, int count = 1) et il a été compilé sans erreur, de sorte que ‘ t le problème dans ce cas, mais vous avez raison de dire que __data na pas été initialisé.
  • should never have compiled – il a testé les fonctions plutôt que dappeler capacity est une fonction membre, pas une variable. Je lai ramassée lorsque jai essayé de la compiler sous clang.
  • Hé hé! Cela ma manqué – et vous ‘ avez raison!
  • un petit conseil: votre code est pratiquement illisible. Utilisez un meilleur style de codage : google.github.io/styleguide/cppguide.html
  • Je ne comprends pas non plus pourquoi vous faites cela. STL nest pas pris en charge, il y en a plusieurs bien debugg e implémentations que vous pouvez trouver avec une simple recherche Google. Si vous le faites pour apprendre le C ++, lenvironnement arduino est probablement la pire façon de le faire!

Réponse

Il y a quelque chose que je voudrais ajouter.
Dautres ont déjà signalé de nombreuses erreurs dans le code. Et je pourrais en souligner beaucoup dautres, mais ce nest pas le but.
IMO, la plus grosse erreur est – implémenter une classe Vector pour lArduino!
Même si ça marche, cest juste une mauvaise idée. Gardez à lesprit que lArduino a une quantité de mémoire TRÈS limitée. Lallocation dynamique de mémoire dans un espace aussi contraint semble juste une mauvaise idée – vous gaspillez de la mémoire, vous fragmentez le tas et vous ne gagnez presque rien, à part une certaine commodité de codage.
Je nai pas encore vu de cas où il y en a vraiment nécessite une allocation de mémoire dynamique dans un projet Arduino. Et même si vous le faites, il existe de nombreux autres outils que vous pouvez utiliser (comme les tampons temporaires alloués par pile, les allocateurs darène, les allocateurs de pool, etc.). Mais encore une fois, le code et le coût de la mémoire ces solutions « génériques » seront irréalisables sur lArduino.
La plupart du temps, cest bien dutiliser des vecteurs, des cartes et tout ce que vous voulez sur des plateformes comme PC (je travaille en tant que programmeur de jeux PC, donc je rencontre souvent des situations où les extras comme ceux-ci sont de gros « non-non », mais pour la plupart des applications ça va).
Mais, sur une plate-forme « serrée » comme lArduino, je pense que vous devriez rester « proche du métal » et être en contrôle total de ce qui se passe (mémoire et cycles CPU). Idéalement, vous ne devriez vous permettre que « luxuri es « comme ceux-ci quand vous savez ce qui se passe » en dessous « et que vous savez que vous pouvez vous en tirer. Ce qui est rarement le cas sur des plates-formes « étroites » comme lArduino.

Commentaires

  • Vous avez un point ici. En particulier, lidée push_back qui alloue de manière incrémentielle plus de mémoire (ce qui est normalement bien sur un PC avec 4 Go de RAM) est susceptible dintroduire une fragmentation de la mémoire. Je pense que même les implémentations STL normales allouent de la mémoire vectorielle (capacité dans ce cas) par lots (par exemple 10 à la fois) pour réduire ce problème.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *