Иерархия классов Arduino, строки и утечка памяти

Добрый день, я запускаю новый проект Arduino 1.6.10 IDE ver. но я сталкиваюсь с некоторыми проблемами утечки памяти, когда использую структуру на основе классов.

Сначала я публикую свой код, а затем указываю место, где появляется утечка памяти.

основной файл эскиза.

#include <Ethernet.h>
#include <MemoryFree.h>
#include "Constants.h"
#include "State.h"



StateFactory CurrentStateFactory;

void setup() {

  pinMode(BUZZER,OUTPUT);
  Serial.begin(9600);
  Serial.println("START");
  delay(1000);

}

void loop() {

  Serial.print(F("Free RAM = ")); 
  Serial.println(freeMemory(), DEC);  // print how much RAM is available.
  CurrentStateFactory.changeStatus(1);
  Serial.println(CurrentStateFactory.getCurrentState()->getNumber());
  CurrentStateFactory.changeStatus(2);
  Serial.println(CurrentStateFactory.getCurrentState()->getNumber());
}

Проблема видимо в State.h я отметил точку в комментах

#ifndef State_h
#define State_h

/////////////////// STATE/////////////////////////

class MachineState{
  public: 
    virtual int getNumber();
  protected:

};

/////////////////////ACTIVE FULL/////////////////////////////////
class ActiveFull : public MachineState
{
  public:
      ActiveFull();
      virtual int getNumber();
  private:
      String statusName; //<----- PROBLRM SEEMS TO BE HERE WHEN COMMENTED NO MEMORY LEAK APPEN
      int number;
};

ActiveFull::ActiveFull(){
  this->number=1;
};


int ActiveFull::getNumber(){
  return this->number;
}

////////////////////////////// ACTIVE EMPTY ////////////////////
class ActiveEmpty : public MachineState
{
  public:
      ActiveEmpty();
      virtual int getNumber();
  protected:
      String statusName;//<----- PROBLRM SEEMS TO BE HERE WHEN COMMENTED NO MEMORY LEAK APPEN
      int number;
};

ActiveEmpty::ActiveEmpty(){
   this->number=2;
};

int ActiveEmpty::getNumber(){
  return this->number;
}


//////////////////FACTORY/////////////////////////////


class StateFactory{
    private:
      MachineState *currentState;
    public: 
      StateFactory();
      void *changeStatus(int choice); // factory
      MachineState *getCurrentState();
  };

StateFactory::StateFactory(){
  MachineState *var1=new ActiveFull();
  this->currentState=var1; 
}

MachineState *StateFactory::getCurrentState(){
  return this->currentState; 
 }


void *StateFactory::changeStatus(int choice)
{
 delete  this->currentState;  // to prevent memory leak
  if (choice == 1){
      MachineState *var1=new ActiveFull();
      this->currentState=var1;
    }
  else if (choice == 2){
      MachineState *var1=new ActiveEmpty;
      this->currentState=var1;
    }
  else{
      MachineState *var1=new ActiveEmpty;
      this->currentState=var1;
    }
}

#endif

я использую библиотеку для отслеживания использования памяти, и это результат скетча:

Нет утечки памяти (строка statusName закомментирована)

Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2
Free RAM = 7897
1
2

Утечка памяти, когда свойство String statusName раскомментировано

Free RAM = 6567
1
2
Free RAM = 6559
1
2
Free RAM = 6551
1
2
Free RAM = 6543
1
2
Free RAM = 6535
1
2
Free RAM = 6527
1
2

Спасибо за совет за ваше время. Надеюсь, вы можете мне помочь.


person groot    schedule 21.12.2016    source источник
comment
Спрашивать ОС о том, сколько свободной памяти доступно, — не лучший способ обнаружить утечку памяти. Причина в том, что ОС предоставляет память кусками. Возможно, для добавления какого-либо члена требуется другой фрагмент, который объясняет ваши свободные номера ОЗУ.   -  person Ripi2    schedule 21.12.2016
comment
Где ваши геттеры и сеттеры для имени?   -  person NonCreature0714    schedule 21.12.2016
comment
Gette и сеттеры statusName не влияют на проблему (я проверял это). Я удалил их, чтобы сделать код короче, прежде чем публиковать его здесь, чтобы обеспечить более быстрое чтение.   -  person groot    schedule 21.12.2016


Ответы (2)


это похоже на проблему деструктора,

Я публикую реализацию на основе вашего кода.

#ifndef State_h
#define State_h


/* MachineState Class */
class MachineState{
  public:
  virtual void test() = 0;
     MachineState(){
        number = 0;
        statusName = "NULL";
     }
     virtual ~MachineState(){
      Serial.println("Destroy base");
     }
     void setNumber(int n){
      number =  n;
     }
     void setStatusName(String some){
      statusName = some;
     }
     String getStatusName(){
      return statusName;
     }
     int getNumber(){
      return number;
     }
     virtual void print()const{
      Serial.println("Class MS");
     }
  protected:
      String statusName;
      int number;

};


/* ActiveFull Class */
class ActiveFull : public MachineState{
  public:
      ActiveFull(){
        x = "Class AF";
        setNumber(1);
      }
      void print()const{
        Serial.println("Class AF"); 
      }
      void test(){}
      ~ActiveFull(){
       Serial.println("Destroy AF");
      }
  private:
    String x;
};


/* ActiveEmpty Class */
class ActiveEmpty : public MachineState
{
  public:
      void print()const{
        Serial.println("Class EE"); 
      }
      ActiveEmpty(){
        x = "Class EE";
        setNumber(2);
      }
      void test(){}
      ~ActiveEmpty(){
          Serial.println("Destroy EE");
      }
  private:
    String x;
};

/* StateFactory Class */
class StateFactory{
    private:
      MachineState *currentState;
    public: 
      StateFactory();
      ~StateFactory(){
        Serial.println("Ho distrutto StateFactory");
      }
      void changeStatus(int choice); // factory
      MachineState *getCurrentState();
  };

StateFactory::StateFactory(){
  this->currentState=new ActiveFull(); 
}

MachineState *StateFactory::getCurrentState(){
  return this->currentState; 
 }


void StateFactory::changeStatus(int choice){
  if(this->currenState)
     delete  this->currentState;
  if (choice == 1){
      currentState = new ActiveFull();
    }
  else if (choice == 2){
      currentState = new ActiveEmpty();
    }
  else{
      currentState = new ActiveEmpty();
    }
}

#endif

Это мой результат с вашим основным:

...

2
Class EE
Free RAM = 7751
Destroy EE
Destroy base
1
Class AF
Destroy AF
Destroy base
2
Class EE
Free RAM = 7751
Destroy EE
Destroy base
1
Class AF
Destroy AF
Destroy base

...
person Francesco Lanza    schedule 02.01.2017
comment
работает тк. Я был на нем несколько дней. С новым годом. - person groot; 02.01.2017
comment
И тебя с Новым годом. - person Francesco Lanza; 02.01.2017

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я хотел опубликовать это как комментарий, а не ответ, потому что, на мой взгляд, это не решает проблему, а просто дает советы. Затем мне понадобились некоторые блоки кода, поэтому мне понадобились функции ответа.

Ну, ваш код ИМХО нуждается в некоторых доработках (а может быть, это только потому, что вы его уменьшили, но все равно я их для вас выложу)

  1. Не помещайте реализацию функции в файл заголовка: используйте файл cpp для хранения реализации функций и файл заголовка для хранения прототипов.
  2. Целью наследования является повторное использование большей части кода, который у вас уже есть. Так что бессмысленно иметь много разных переменных; гораздо лучше объявить их по-другому.

Например, вы можете использовать его так:

/* File State.h */

class MachineState{
    public: 
        int getNumber();
    protected:
        String statusName;
        int number;
};

/////////////////////ACTIVE FULL/////////////////////////////////
class ActiveFull : public MachineState
{
    public:
        ActiveFull();
};

////////////////////////////// ACTIVE EMPTY ////////////////////
class ActiveEmpty : public MachineState
{
    public:
        ActiveEmpty();
};

/* File State.cpp */

int MachineState::getNumber(){
    return this->number;
}

ActiveEmpty::ActiveEmpty(){
    this->number=1;
};

ActiveEmpty::ActiveEmpty(){
    this->number=2;
};

или, если вам не нужно менять значение числа (и поэтому вам не нужна реальная переменная)

/* File State.h */

class MachineState{
    public: 
        virtual int getNumber() = 0;
    protected:
        String statusName;
};

/////////////////////ACTIVE FULL/////////////////////////////////
class ActiveFull : public MachineState
{
    public:
        virtual int getNumber();
};

////////////////////////////// ACTIVE EMPTY ////////////////////
class ActiveEmpty : public MachineState
{
    public:
        virtual int getNumber();
};

/* File State.cpp */

int ActiveEmpty::getNumber(){
    return 1;
};

int ActiveEmpty::getNumber(){
    return 2;
};

Затем есть небольшая проблема с освобождением: если new не удастся, вы получите проблемы на следующем delete. Чтобы решить эту проблему, вы можете сделать что-то вроде (и я также немного сократил ваш код)

void *StateFactory::changeStatus(int choice)
{
    if (this->currentState) // If it was correctly allocated
        delete this->currentState;  // to prevent memory leak
    switch (choice)
    {
    case 1:
        this->currentState = new ActiveFull();
        break;
    case 2: // case 2 can be removed since it is identical to default
        this->currentState = new ActiveEmpty();
        break;
    default:
        this->currentState = new ActiveEmpty();
        break;
    }
}

Тем не менее... Ну, я бы изменил цикл следующим образом:

void printCurrentStateNumber()
{
    if (CurrentStateFactory.getCurrentState())
        Serial.println(CurrentStateFactory.getCurrentState()->getNumber());
    else
        Serial.println("No more memory");
}

void loop() {
    Serial.print(F("Free RAM = ")); 
    Serial.println(freeMemory(), DEC);  // print how much RAM is available.
    CurrentStateFactory.changeStatus(1);
    printCurrentStateNumber();
    CurrentStateFactory.changeStatus(2);
    printCurrentStateNumber();
}

Это должно проверить, было ли состояние успешно создано.

Что касается вашей явной проблемы, я не знаю, как работает библиотечная функция. Прежде чем начать понимать, почему происходит эта утечка, я бы попытался выяснить, действительно ли это утечка. Поэтому запустите модифицированную программу (с проверкой перед удалением и печатью строки отсутствия памяти) и дайте ей работать, пока библиотека не сообщит вам, что ей не хватает памяти. Если он стабилизируется или достигает 0 и не печатает это, это проблема библиотеки. С другой стороны, если программа перестает печатать строку, это утечка.

Одно замечание: не стоит позволять маленькому микроконтроллеру слишком часто выполнять выделение и освобождение памяти, поскольку он имеет ограниченный объем памяти. Проведите тест, потому что, если есть реальная утечка, возможно, ее следует изучить больше, но для вашего приложения я предлагаю вам подумать о постоянном размещении двух экземпляров объекта, а затем просто использовать их в соответствии со значением, которое вы передали ему раньше - очевидно если есть только пара производных классов), например:

/* In the header file */
#define NUM_OF_STATES 2

class StateFactory{
private:
    MachineState states[NUM_OF_STATES];
public: 
    StateFactory();
    void changeStatus(int choice); // factory
    MachineState *getCurrentState();
private:
    int currentIdx;
};

/* In the source file */

StateFactory::StateFactory()
{
    states[0] = new ActiveFull();
    states[1] = new ActiveEmpty();
    this->currentIdx = 0;
}

MachineState *StateFactory::getCurrentState(){
    return states[this->currentIdx];
}

void StateFactory::changeStatus(int choice)
{
    switch (choice)
    {
    case 1:
        this->currentIdx = 0;
        break;
    case 2: // case 2 can be removed since it is identical to default
        this->currentIdx = 1;
        break;
    default:
        this->currentIdx = 1;
        break;
    }
}

ПОСЛЕДНЕЕ ПРИМЕЧАНИЕ. Пересмотрев ответ, я обнаружил, что ваша функция changeStatus возвращает void * вместо void. Вы обязательно должны исправить это, и МОЖЕТ БЫТЬ, что все будет исправлено (на самом деле вы возвращаете указатель, а не ничего). Но я не уверен в этом.

person frarugi87    schedule 22.12.2016
comment
Спасибо за ваш вклад. Как вы намекнули, код очень простой, я удалил все ненужное, чтобы помочь читателям быстрее понять проблему и избежать каждой второстепенной проблемы или ошибки, сосредоточив внимание только на методе, который вызывает проблему. - person groot; 22.12.2016
comment
Как вы намекнули, код очень простой, я удалил все ненужное, чтобы помочь читателям быстрее понять проблему и избежать каждой второстепенной проблемы или ошибки, сосредоточив внимание только на методе, который дает проблему. то же самое для констант в коде и недостающий сеттер и геттер. Сказал, что я нахожу очень интересными ваши аргументы, и я попробую передать это. Вы были правы в ошибке в функции, которая вернула бесполезный указатель, к сожалению, я удалил его, но утечка памяти все еще присутствует. - person groot; 22.12.2016
comment
Рад, что смог хотя бы дать несколько советов. Надеюсь, вы найдете проблему (и вернетесь, чтобы поделиться ею, так как теперь мне интересно узнать о решении). Кстати, вы можете попробовать выделить строку динамически и удалить ее в деструкторе элемента данных. - person frarugi87; 22.12.2016
comment
Я думаю, проблема в классе String Arduino. Он не должен быть постоянным, поэтому я не могу удалить его в деструкторе. - person groot; 23.12.2016
comment
Что вы подразумеваете под не должно быть постоянным, поэтому я не могу его удалить? Вы можете создать его в куче в конструкторе, а затем удалить в деструкторе... - person frarugi87; 23.12.2016
comment
К сожалению, кажется, что это не работает со строкой класса Arduino, если я реализую деструктор следующим образом: ActiveEmpty::~ActiveEmpty(){ delete this->statusName ; } я получаю следующую ошибку: аргумент type 'class String' указан для 'delete', ожидаемый указатель - person groot; 02.01.2017