ArduinoでVectorクラスを構築する

私は、回避できるように見えるこの問題に苦労しています。ベクトルクラスを定義するとき、私は割り当てられたポインタを削除するときにいくつかの問題が発生します。

奇妙なことに、これはメモリを「読み取った」後にのみ発生します。これが意味するのは、一連の値をベクトルにプッシュバックすると、それらはそこにとどまっているように見えるということです。しかし、値を読み取ると、データポインターがなんらかの形で無効になっているように見えるため、割り当てを解除しようとするとクラッシュします。

コード:

#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 

Arduinoでこのコードを使用していましたが、役立つかどうかわかりません

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

このコードの出力:新しいvecを印刷しています! 10 2新しいvecを印刷します! 0 2 3新しいvecを印刷しています! 0 2新しいvecを印刷しています! 0(…)

これを引き起こしている可能性があるのは何ですか?私はしばらくの間妨害されていますが、これを解決する方法についての洞察をいただければ幸いです。ありがとうございます!

コメント

  • 最後にコード出力は、表示される出力の「(…)」部分であるかどうかを示しますか?’ tでない場合、出力は予想とどのように異なりますか?ベクトルに4つのアイテムがある場合、4つのi.pop_back()呼び出しでベクトルを空にする必要があります。
  • delete this->__data; //program crashes here ... delete[]を使用する必要がありますか?また、クラスにプラットフォームコードがないため、はるかに優れたデバッグツールが利用できるPCでデバッグできます。

回答

コードに多くの問題があったため、どこから始めればよいかわかりません。 🙂

JohnBurgerの発言に同意します。

コードをPCに取り込んで、毎回アップロードする手間を省き、valgrindを使用できるようにしました。確かに、ベクターを印刷した後、valgrindがエラーを報告しました。その理由は単純です。クラスのコピーprint_vecに渡しました。これは、デストラクタが取得したことを意味します。 print_vecが終了したときに呼び出され、メモリの割り当てが解除されます。 コピーコンストラクタが必要でした。これがないと、コンパイラはクラスのビット単位のコピーを作成します。つまり、2つのオブジェクトが同じ割り当てメモリを共有することになります。

手っ取り早い修正は、print_vec参照:

void print_vec(vector<int> & v){ 

ただし、将来的にはバグが潜んでいます。

この例ではコピーコンストラクタを実装しました。ただし、以下では、参照によりprint_vecを呼び出すと、コピーする必要のあるクラスが節約され、実行するnew/deleteの数が減り、場合によっては減ることがあります。メモリの断片化。


ジョンバーガーが言ったように:自分でコンストラクタを呼び出さないでください!あなたそれはできません。デストラクタとclear関数で同じことをしたい場合は、デストラクタにclear()


変数で先頭の二重下線を使用することは、C ++標準に反します。そうしないでください。メンバー変数を示す場合は、末尾にアンダースコアを使用します。 this->の参照をすべて失います。混乱するだけです。


配列を割り当てるので、delete []を使用する必要があります。これは1つの場所で行いましたが、他の場所では行いませんでした。


なぜこれを行うのですか?これは不要な割り当てです:

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

実行するだけです:

 vector<int> i; 

何らかの理由でクラスを割り当てる場合は、operator=も実装する必要があります。そうしないと、コピーコンストラクターで発生したのと同じ問題が発生します(私の例を参照)。


ここでは、関数(容量とサイズ)を呼び出さずにテストしています:

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

実際にはこれを行うことはできません:

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

「else」パスを使用すると、関数は値を返しません(警告をオンにすると、コンパイラの警告が表示されます)。


私の提案を含む、作り直した例。警告やエラーなしでコンパイルされ、クラッシュすることなく実行されます(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(); } } 

コメント

  • わかりました、どうもありがとうございました!多くの問題は私の側の単なる気晴らしでした。私は最初に直面していたメモリの問題を修正することに集中しようとしていました。

回答

2番目のコンストラクターでは、次のコードで開始します。

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

これは致命的です! __dataを初期化していないため、太陽の下で任意の値を保持できます。また、方法はありません。 おそらく既存のデータへの有効なポインタである-それは “真新しい初期化されていないオブジェクトです。このガベージポインタをヒープに戻すことは、単に問題を求めているだけです。

これらの行を削除します。または、最初のコンストラクタで行ったようにイニシャライザリストを使用することをお勧めします。

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) 

別の問題:作成したclear()メンバー:

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

絶対に、絶対に決してこのようなデストラクタを直接呼び出すことはありません。コンパイラは、デストラクタコードを呼び出す唯一のコードであることを「認識」しているため、他のあらゆる種類のコードをデストラクタコードに含めることができます。たとえば、一部のコンパイラは、フラグを事前にプッシュして、deleteデストラクタを実行した後、ヒープからオブジェクトを取得します。まだ実行していないため、上記の方法であらゆる種類のものが破損する可能性があります。

代わりに、デストラクタをclear()に移動し、デストラクタからclear()を呼び出すだけです。

もう1つ、これがピックアップされました@Nick Gammon:

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

これは、__dataメンバーがfalseであるかどうか、およびcapacityおよびsize関数が定義されました。心配しないでください。後者の2つは…プレフィックス__

[また、どこでもthis->で停止します。すべてのメンバー変数の前に__を使用しました(これ自体が慣例に反しています) );それらがメンバー変数であるという事実を打ち明ける必要はありません。コンパイラーはすでに知っています。]

コメント

  • vector(T obj, int count = 1)コンストラクター全体を削除しましたが、エラーなしでコンパイルされたため、’この問題は発生しませんでした。ケースですが、__dataが初期化されていないのは正しいです。
  • should never have compiled-呼び出すのではなく関数をテストしましたcapacityはメンバー関数であり、変数ではありません。clangでコンパイルしようとしたときにそれを見つけました。
  • Hee hee!私はそれを見逃しました-そしてあなたは’正しいです!
  • ちょっとしたアドバイス:あなたのコードは実質的に読めません。より良いコーディングスタイルを使用してください: google.github.io/styleguide/cppguide.html
  • また、なぜこれを行っているのかわかりません。本格的ですがSTLはサポートされていません。いくつかの適切なデバッグがあります。簡単なグーグル検索で見つけることができるedの実装。 C ++を学ぶためにそれを行っている場合、arduino環境はおそらくそれを行うための最悪の方法です!

回答

追加したいことがあります。
他の人はすでにコードの多くのエラーを指摘しています。そして、私はもっと多くのことを指摘することができましたが、それは重要ではありません。
IMO、最大の間違いは-Arduino用のVectorクラスを実装することです!
たとえうまくいったとしても、それは悪い考えです。 Arduinoのメモリ量は非常に限られていることに注意してください。このような制約のあるスペースでの動的メモリ割り当ては、悪い考えのように聞こえます。メモリを浪費し、ヒープを断片化し、コーディングの利便性を除いて、ほとんど何も得られません。
実際に1つが実際に発生するケースはまだ見ていません。 Arduinoプロジェクトでは動的なメモリ割り当てが必要です。そうしても、使用できるツールは他にもたくさんあります(スタック割り当ての一時バッファ、アリーナアロケータ、プールアロケータなど)。ただし、おそらくコードとメモリのコストはこれらの「一般的な」ソリューションは、Arduinoでは実行できません。
ほとんどの場合、PCなどのプラットフォームでベクターやマップなどを使用しても問題ありません(私はPCゲームプログラマーとして働いているため、状況に遭遇することがよくあります)このようなエクストラは大きな「ノーノー」ですが、ほとんどのアプリケーションでは問題ありません。
しかし、Arduinoのような「タイトな」プラットフォームでは、「金属の近く」にとどまり、何が起こっているかを完全に制御します(メモリとCPUサイクルに関して)。理想的には、「贅沢」だけを買う余裕があるはずです。 es」は、「下」で何が起こっているかを知っていて、それを回避できることがわかっている場合に、このようになります。これは、Arduinoのような「タイトな」プラットフォームではめったにありません。

コメント

  • ここにポイントがあります。特に、より多くのメモリを段階的に割り当てるpush_backのアイデア(通常、4 GbのRAMを搭載したPCでは問題ありません)は、メモリの断片化を引き起こす可能性があります。通常のSTL実装でさえ、この問題を減らすために、ベクトルメモリ(この場合は容量)をバッチ(たとえば、一度に10)で割り当てていると思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です