아두 이노에서 벡터 클래스 만들기

나는이 문제를 해결하려고 애 쓰고 있습니다. 벡터 클래스를 정의 할 때 저는 할당 된 포인터를 삭제할 때 문제가 발생합니다.

이상하게도 이것은 메모리를 “읽은”후에 만 발생합니다. 이것이 의미하는 바는 일련의 값을 벡터로 밀어 넣으면 그 값이 그대로 유지되는 것처럼 보입니다. 하지만 일단 값을 읽으면 데이터 포인터가 일종의 무효화되어 할당을 해제하려고 할 때 충돌이 발생합니다.

코드 :

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

이 코드는 다음을 출력합니다. Printing new 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에서 디버깅 할 수 있습니다.

답변

코드에 많은 문제가있어서 어디서부터 시작해야할지 알기 어렵습니다. 🙂

John Burger의 말에 동의합니다.

매번 업로드 할 때마다 엉망이되는 시간을 절약하기 위해 귀하의 코드를 내 PC로 가져 갔으며 또한 valgrind를 사용할 수있었습니다. 확실히 valgrind는 벡터를 인쇄 한 후 오류를보고했습니다. 그 이유는 간단합니다. 클래스의 사본 print_vec에 전달했으며 이는 소멸자가 print_vec가 종료 될 때 호출되어 메모리 할당을 해제합니다. 복사 생성자 가 있어야합니다. 이것이 없으면 컴파일러는 클래스의 비트 복사본을 만듭니다. 즉, 이제 동일한 할당 된 메모리를 공유하는 두 개의 개체가 있습니다.

빠르고 더러운 수정은 print_vec 참조 :

void print_vec(vector<int> & v){ 

하지만 이로 인해 향후 버그가 숨어 있습니다.

예제에서 복사 생성자를 구현했습니다. 그러나 아래에서 print_vec를 참조로 호출하면 복사해야하는 클래스가 절약되고 수행중인 new/delete의 수가 줄어들어 메모리 조각화입니다.


John Burger가 말했듯이 : 소멸자를 직접 호출하지 마십시오! 그렇게 할 수 없습니다. 소멸자와 clear 함수에서 동일한 작업을 수행하려면 소멸자가 clear().


변수에 선행 이중 밑줄을 사용하는 것은 C ++ 표준에 위배됩니다. 그렇게하지 마십시오. 멤버 변수를 나타내려면 후행 밑줄을 사용하십시오. 모든 this-> 참조를 잃습니다. 그들은 단지 일을 복잡하게 만듭니다.


배열을 할당 했으므로 delete []를 사용해야합니다. 한 곳에서 수행했지만 다른 곳에서는 사용하지 않았습니다.


p>


그 이유는 무엇입니까? 불필요한 할당입니다.

 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”경로를 사용하면 함수가 값을 반환하지 않습니다 (경고를 켜면 컴파일러 경고가 표시됨).

p>


제 제안과 함께 수정 된 예. 경고 또는 오류없이 컴파일되며 충돌없이 실행됩니다 (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(); } } 

댓글

  • 좋아요, 정말 고마워요! 대부분의 문제는 제 입장에서 평범한 산만 함이었고 처음에 직면했던 기억 문제를 해결하는 데 집중하려고 노력했습니다.

답변

두 번째 생성자에서 다음 코드로 시작합니다.

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) 

다른 문제 : In 작성한 clear() 회원 :

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

절대 절대 , 절대 이와 같은 소멸자를 직접 호출하지 않습니다. 컴파일러는 소멸자 코드에 모든 종류의 다른 코드를 넣을 수 있습니다. 이는 자신이 호출 할 유일한 코드임을 “알고”있기 때문입니다. 예를 들어 일부 컴파일러는 delete 삭제 후 힙에서 객체를 가져옵니다. 그렇게하지 않았으므로 위의 작업은 모든 종류의 문제를 일으킬 수 있습니다.

대신에있는 코드를 이동합니다. 소멸자를 clear()에 입력 한 다음 소멸자에서 clear()를 호출하면됩니다.

다른 하나,이 항목이 선택되었습니다. 작성자 : @Nick Gammon :

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

__data 멤버가 거짓인지 여부와 capacitysize 함수가 정의되었습니다. 걱정하지 마세요. 후자의 두 가지는 … 접두사 __

[또한 모든 곳에서 this->를 사용하여 중지하십시오. 모든 멤버 변수 앞에 __를 사용했습니다 (그 자체가 관례에 위배됩니다. ); 컴파일러는 이미 알고 있습니다.]

코멘트

  • 전체 vector(T obj, int count = 1) 생성자를 삭제했고 오류없이 컴파일되었으므로 ‘이 문제가 아닙니다. 하지만 __data가 초기화되지 않은 것은 맞습니다.
  • should never have compiled-호출하는 대신 함수를 테스트했습니다. capacity는 변수가 아니라 멤버 함수입니다. clang에서 컴파일을 시도했을 때이를 선택했습니다.
  • 희희! 놓쳤습니다. ‘ 맞습니다.
  • 조금 조언 : 코드를 읽을 수 없습니다. 더 나은 코딩 스타일을 사용하세요. : google.github.io/styleguide/cppguide.html
  • 또한이 작업을 수행하는 이유도 알 수 없습니다. STL은 지원되지 않으며 몇 가지 문제가 있습니다. 간단한 Google 검색으로 찾을 수있는 ed 구현입니다. C ++를 배우기 위해 수행하는 경우 arduino 환경이 아마도 최악의 방법 일 것입니다!

Answer

추가하고 싶은 것이 있습니다.
다른 사람들은 이미 코드에서 수많은 오류를 지적했습니다. 그리고 더 많은 것을 지적 할 수 있지만 그게 요점이 아닙니다.
IMO, 가장 큰 실수는 Arduino를위한 Vector 클래스를 구현하는 것입니다!
효과가 있더라도 나쁜 생각입니다. Arduino는 매우 제한된 양의 메모리를 가지고 있습니다. 이렇게 제한된 공간에서 동적 메모리 할당은 나쁜 생각처럼 들립니다. 메모리를 낭비하고 “힙을 조각화하고 코딩 편의를 제외하고는 거의 아무것도 얻지 못합니다.
실제로 하나가 실제로 발생하는 경우를 보지 못했습니다. Arduino 프로젝트에서 동적 메모리 할당이 필요합니다. 그리고 그렇게하더라도 사용할 수있는 다른 도구가 많이 있습니다 (스택 할당 임시 버퍼, 아레나 할당 자, 풀 할당 자 등). 그러나 다시 말하지만 코드 및 메모리 비용은 이러한 “일반적인”솔루션은 Arduino에서 실행 불가능합니다.
대부분의 경우 PC와 같은 플랫폼에서 벡터,지도 및 원하는 것을 사용하는 것이 좋습니다 (저는 PC 게임 프로그래머로 일하기 때문에 종종 상황에 직면합니다. 이와 같은 추가 기능은 “아니요”입니다.하지만 대부분의 애플리케이션에서는 괜찮습니다.)
하지만 Arduino와 같은 “단단한”플랫폼에서는 “금속에 가깝게”유지해야한다고 생각합니다. 무슨 일이 일어나고 있는지 (메모리와 CPU주기가 현명하게) 완전히 제어 할 수 있습니다. 이상적으로는 “럭셔리 es “는”아래 “에서 무슨 일이 일어나고 있는지 알고 있고 그것으로 빠져 나갈 수 있다는 것을 알고있을 때 이렇게 말합니다. Arduino와 같은 “단단한”플랫폼에서는 드물게 발생합니다.

댓글

  • 여기에 요점이 있습니다. 특히 더 많은 메모리를 점진적으로 할당하는 push_back 아이디어 (일반적으로 4Gb의 RAM이있는 PC에서 괜찮음)는 메모리 조각화를 일으킬 가능성이 있습니다. 일반적인 STL 구현에서도 벡터 메모리 (이 경우 용량)를 일괄 (예 : 한 번에 10 개)로 할당하여이 문제를 줄일 수 있다고 생각합니다.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다