Vektoriluokan rakentaminen Arduinoon

Olen kamppailen tämän ongelman kanssa, josta voin näyttää kiertyvän. Määritellessäsi vektoriluokkaa, minusta tuntuu saada joitain ongelmia varatun osoittimen poistamisessa.

Kummallakin tavalla tämä tapahtuu vasta, kun olen lukenut muistin. Tarkoitan tällä sitä, että jos työnnän vektorille takaisin joukon arvoja, ne näyttävät pysyvän siellä. Mutta kun olen lukenut arvot, datan osoitin näyttää olevan jonkin verran virheellinen, joten kaatuin yrittäessäsi kohdistaa sen.

Koodi:

#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 

Käytin tätä koodia Arduinossa, en ole varma siitä, auttaako se

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

Tämä koodi antaa tuloksen: Tulostetaan uusi vec! 10 2 Uusien vec! 0 2 3 Uusien vec! 0 2 Uusien vec! 0 (…)

Mikä voi aiheuttaa tämän? Olen kivimuurattu jonkin aikaa, kaikki oivallukset tämän ratkaisemiseksi ovat kiitollisia. Kiitos!

Kommentit

  • Lopuksi sanotaanko koodilähdöt, on ”(…)” osa näkemääsi lähtöä vai ei? Jos se ei ole ’ t, miten lähtö eroaa odotetusta Jos vektorissa on neljä kohdetta, neljän i.pop_back() -kutsun pitäisi tyhjentää vektori.
  • delete this->__data; //program crashes here ... Pitäisikö käyttää delete[] eikö? Luokassasi ei myöskään ole alustakoodia, joten voit debugata tietokoneella, jossa on paljon parempia virheenkorjaustyökaluja.

Vastaus

Sinulla oli paljon ongelmia koodisi kanssa, joten on vaikea tietää mistä aloittaa. 🙂

Olen samaa mieltä siitä, mitä John Burger sanoi.

Otin koodisi tietokoneelleni säästääksesi nauhoitusta sen lataamisen yhteydessä joka kerta, ja voisin myös käyttää valgrindia siinä. Varmasti valgrind ilmoitti virheestä, kun olet tulostanut vektorin. Syy siihen on yksinkertainen. Ohitit luokan kopion luokassa print_vec, mikä tarkoitti, että tuhoaja sai soitettiin, kun print_vec poistui, jakoi siten muistin. Sinulla olisi pitänyt olla kopiosuunnittelija . Ilman sitä kääntäjä luo luokan bittikopion, mikä tarkoittaa, että sinulla on nyt kaksi objektia, jotka jakavat saman varatun muistin.

Nopea ja likainen korjaus on kutsua print_vec viitteellä:

void print_vec(vector<int> & v){ 

Tämä jättää kuitenkin virheen tulevaisuuden varalle.

Toteutin kopiorakentajan esimerkissäni alla, mutta kutsuminen viitteellä print_vec säästää kopioitavan luokan ja vähentää tekemiesi new/delete -määrien määrää ja siten vähentää muistin pirstaloituminen.


Kuten John Burger sanoi: älä kutsu itse tuhoajaa! Sinä ei voi tehdä sitä. Jos haluat tehdä saman asian destruktorissa ja clear -funktiossa, anna vain destruktorin kutsua clear().


Johtavien kaksoisviivojen käyttö muuttujissa on C ++ -standardin vastaista. Älä tee sitä. Käytä alaviivaa, jos haluat ilmoittaa jäsenmuuttujan. Menettää kaikki this-> -viitteet. Ne vain sekoittavat asiat.


Koska varat matriisin, sinun on käytettävä delete [] – teit sen yhdessä paikassa, mutta et toisessa.


Miksi tämä tapahtuu? Se on tarpeeton tehtävä:

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

Tee vain:

 vector<int> i; 

Jos jos aiot määrittää luokan jostakin syystä, sinun on myös otettava käyttöön operator=, tai sinulla on samat ongelmat kuin kopiorakentajan kanssa. (Katso esimerkki).


Tässä testataan toimintoja (kapasiteettia ja kokoa) kutsumatta niitä:

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

Et voi tehdä tätä :

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

Jos valitset ”muu” polun, funktio ei palauta arvoa (saat kääntäjän varoituksen, jos otat varoitukset käyttöön).


Muokattu esimerkkini ehdotuksineen. Se kootaan ilman varoituksia tai virheitä ja toimii kaatumasta (tietokoneella):

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

kommentit

  • Selvä, kiitos paljon! Monet ongelmat olivat pelkkää häiriötekijää päälleni, yritin keskittyä korjaamaan aluksi kohtaamani muistiongelmat.

Vastaa

Toisessa konstruktorissasi aloitat seuraavalla koodilla:

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

Tämä on tappavaa! Et ole alustanut __data, joten se voi pitää mitä tahansa arvoa auringon alla. Ja ei ole mitään keinoa jota se voisi mahdollisesti olla kelvollinen osoitin olemassa oleville tiedoille – se on upouusi alustamaton objekti. Tämän roskakorin palauttaminen kasaan vaatii vain ongelmia.

Poista nuo rivit – tai vielä parempi, käytä alustusluetteloa kuten ensimmäisen konstruktorin kohdalla:

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) 

Toinen ongelma: kirjoittamasi clear() -jäsenesi:

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

Älä koskaan, ei koskaan , ei koskaan kutsu suoraan tällaista tuhoajaa. Kääntäjä voi laittaa kaikenlaisia muita koodeja tuhoajakoodiin, koska se ”tietää”, että se on ainoa, joka sitä koskaan kutsuu. Esimerkiksi jotkut kääntäjät työntävät lippua ennalta sanoakseen delete esine kasasta tuhon tekemisen jälkeen. Et ole tehnyt niin, joten yllä olevat voivat vioittaa kaikenlaisia asioita.

Siirrä sen sijaan koodi, joka on destruktori osaksi clear() ja kutsu sitten yksinkertaisesti clear() destruktorista.

Toinen, tämä otti kirjoittanut @Nick Gammon:

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

Tämä testaa, onko __data -jäsen väärä ja onko capacity – ja size -funktiot on määritelty. Älä huoli: kaksi viimeksi mainittua on ollut … et jäänyt etuliitteeseen __

); sinun ei tarvitse lyödä kotiin sitä, että he ovat jäsenmuuttujia: kääntäjä tietää jo.]

Kommentit

  • Poistin koko vector(T obj, int count = 1) -rakentajan ja se koottiin ilman virheitä, joten ’ ei ollut ongelma tässä tapaus, mutta olet oikeassa, että __data ei alustettu.
  • should never have compiled – se testasi toimintoja soittamisen sijaan ne. capacity on jäsenfunktio, ei muuttuja. Otin sen esille, kun yritin koota sitä kohdassa clang.
  • Hei hei! Kaipasin sitä – ja olet ’ oikeassa!
  • vähän neuvoja: Koodisi on käytännössä lukukelvoton. Käytä parempaa koodaustyyliä : google.github.io/styleguide/cppguide.html
  • En myöskään ymmärrä, miksi teet tämän. Vaikka täysimittainen STL: ää ei tueta, on useita virheenkorjauksia muokkauksia, jotka löydät yksinkertaisesta google-hausta. Jos teet sen oppiaksesi C ++: n, arduino-ympäristö on luultavasti pahin tapa tehdä se!

Vastaa

On jotain, jonka haluaisin lisätä.
Toiset ovat jo huomauttaneet lukuisista virheistä koodissa. Ja voisin osoittaa paljon enemmän, mutta se ei ole asia. – IMO, suurin virhe on Vector-luokan toteuttaminen Arduinolle!
Vaikka se toimii, se on vain huono idea. Muista, että Arduinolla on ERITTÄIN rajoitettu määrä muistia. Dynaaminen muistin allokointi niin rajoitetussa tilassa kuulostaa vain huonolta ajatukselta – tuhlataan muistia, pirstelet kasan ja et saa melkein mitään, lukuun ottamatta koodauksen helppoutta.
En ole vielä nähnyt tapausta, jossa yksi todella olisi vaatii dynaamista muistin allokointia Arduino-projektissa. Ja vaikka niin olisitkin, voit käyttää monia muita työkaluja (kuten pinolle varatut temp-puskurit, areena-allokaattorit, poolien allokaattorit ja mitä muuta). Mutta jälleen kerran luultavasti koodin ja muistin kustannukset nämä ”yleiset” ratkaisut eivät ole toteutettavissa Arduinossa.
Useimmiten on hienoa käyttää vektoreita, karttoja ja mitä tahansa haluamallasi alustalla kuten PC (työskentelen PC-pelien ohjelmoijana, joten kohtaan usein tilanteita missä tällaiset extrat ovat suuria ”ei-ei”, mutta useimmissa sovelluksissa se on hieno).
Mutta ”tiukalla” alustalla, kuten Arduino, mielestäni sinun pitäisi pysyä ”lähellä metallia” ja olla hallitsemaan mitä tapahtuu (muistin ja suorittimen jaksot viisaasti). Ihannetapauksessa sinulla on varaa vain ”luxuriin” ”kuten nämä, kun tiedät mitä tapahtuu” alla ”ja tiedät, että pääset siitä. Näin on harvoin ”tiukoilla” alustoilla, kuten Arduino.

Kommentit

  • Sinulla on asia tässä. Erityisesti push_back -idea, joka jakaa asteittain enemmän muistia (mikä on normaalisti hienoa 4 Gt: n RAM-muistia sisältävällä tietokoneella), todennäköisesti aiheuttaa muistin pirstoutumisen. Luulen, että jopa normaalit STL-toteutukset varaavat vektorimuistin (tässä tapauksessa kapasiteetin) erissä (esim. 10 kerrallaan) tämän ongelman vähentämiseksi.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *