【问题标题】:Arduino class hierarchy, Strings and memory leakArduino 类层次结构、字符串和内存泄漏
【发布时间】:2016-12-21 15:32:49
【问题描述】:

下午好,我正在开始一个新的 Arduino Project 1.6.10 IDE 版本。但是当我使用基于类的结构时遇到了一些内存泄漏问题。

我先发布我的代码,然后我会指出似乎出现内存泄漏的位置。

mainSketchFile.

#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.hi 我在 cmets 中标记了点

#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

我使用库来跟踪内存使用情况,这是草图的输出:

没有内存泄漏(字符串状态名已注释)

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

取消注释属性字符串 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

感谢您的时间建议。希望你能帮助我。

【问题讨论】:

  • 询问操作系统有多少空闲内存可用并不是检测内存泄漏的好方法。原因是操作系统以块的形式提供内存。添加一些成员可能需要另一个块,这将解释您的可用 RAM 数量。
  • name 的 getter 和 setter 在哪里?
  • Gette 和 statusName 的设置者对问题没有影响(我已经测试过了)。我已删除它们以缩短代码,然后将其发布在此处以加快阅读速度。

标签: c++ string memory-leaks arduino class-hierarchy


【解决方案1】:

好像是析构函数的问题,

我根据您的代码发布了一个实现..

#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

...

【讨论】:

  • 它有效。几天前我就在上面。新年快乐。
  • 也祝你新年快乐。
【解决方案2】:

免责声明:我想将其发布为评论,而不是答案,因为在我看来,它并不能解决问题,而只是提供建议。然后我需要一些代码块,所以我需要答案功能。

好吧,恕我直言,您的代码需要一些改进(或者这可能只是因为您减少了它,但无论如何我会为您发布它们)

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

这是为了测试状态是否创建成功。

至于你的明确问题,我不知道库函数是如何工作的。在开始了解为什么会发生这种泄漏之前,我会尝试弄清楚这是否真的是泄漏。因此,启动修改后的程序(在删除之前进行测试并打印 no more memory 字符串)并让它运行,直到库告诉您它内存不足。如果它稳定或达到 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。你绝对应该解决这个问题,也许事情会得到解决(实际上你正在返回一个指针而不是什么)。但我不确定。

【讨论】:

  • Tx 供您贡献。正如您所暗示的那样,代码非常基本,我已经删除了所有不必要的内容,以帮助读者更快地理解问题并避免每个次要问题或错误只关注产生问题的方法。
  • 正如您所暗示的,代码非常基本,我已经删除了所有不必要的内容,以帮助读者更快地理解问题并避免每个次要问题或错误只关注给出相同问题的方法代码中的常量以及缺少的 setter 和 getter。说我觉得你的论点很有趣,我会为此尝试一下。您在函数中关于返回无用指针的错误是正确的,不幸的是我已经删除了它,但内存泄漏仍然存在。
  • 很高兴我至少可以给出一些提示。我希望你能找到问题(并回来分享它,因为现在我对解决方案很好奇)。顺便说一句,您可以尝试动态分配字符串并在项目析构函数中将其删除
  • 问题是我认为的 Arduino 的“字符串”类。它不应该是持久的,所以我不能在析构函数中删除它。
  • “不应该持久化所以我不能删除它”是什么意思?可以在构造函数的堆上创建,然后在析构函数中删除...
猜你喜欢
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 2011-08-29
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 2016-03-28
相关资源
最近更新 更多