Ошибка выполнения при использовании массива структур неизвестного размера в качестве переменной внутри класса — какая-то проблема переполнения?

У меня есть класс, определенный ниже. По сути, это простой кольцевой буфер с некоторыми методами для получения полезной статистики из данных, хранящихся в буфере.

enum BalanceMsgType{
  BAL_MSG_NOT = 0,
  BAL_MSG_READING = 1,
  BAL_MSG_SPECIAL = 2,
  BAL_MSG_ERROR = 3
};

typedef struct MeasuredMass{
  float mg;
  unsigned long time;
  bool steady;
  BalanceMsgType type;
};

class RingBuffer{
  private:
  unsigned int _size = 0;
  unsigned int _pos = 0;
  MeasuredMass *_array;
  void LinearRegression(){
    //... опущено для простоты
  }
  public:
  float lin_reg_m = 0;
  float lin_reg_c = 0;
  float lin_reg_r = 0;
  float est_current_mg = 0;
  //методы
  RingBuffer(unsigned int size){
    _size = size; //забыл вставить эту строку ->div0-> >1 час отладки :(
    _array = new MeasuredMass[_size];
    for (int i = 0; i < _size; i++){
      _array[i].mg = 0.0;
    }
  }
  void Add(MeasuredMass in_element){
    _array[_pos] = in_element;
    _pos = (_pos + 1)%_size;
    LinearRegression();
    est_current_mg = lin_reg_m*(in_element.time+BALANCE_DELAY) + lin_reg_c;
  }
  void ReadInOrder(MeasuredMass *pdata){  //Вызов с
                                          //my_buffer.ReadInOrder(данные)
    for(int i = 0; i < _size; i++){
      pdata[i]=_array[(_pos+i+1)%_size];
    }
  }
  float Sum(){
    //...
  }
  float Mean(){
    //...
  }
  float StdDev(){
    //...
  }
  bool SteadyEnough(float max_sd){
    //...
  }
};

Он отлично компилируется и прекрасно работает в тестовых программах. Однако, если я включаю его в более крупные программы (что-то вроде progmem 117404 из 122880 байт, global vars 13604 из 32768), я получаю предупреждение «Плата на COM8 недоступна» при использовании Arduino Serial Monitor после загрузки в мой Fubarino Mini. (Даже без фактического взаимодействия с этими программами, кроме создания экземпляра класса.) Недавний опыт подсказывает мне, что это какой-то сбой из-за ошибки времени выполнения.

Повозившись с кодом, я обнаружил, что проблема не возникает в версии библиотеки, которая содержит массив float вместо моего MeasuredMass. То, что моя структура MeasuredMass, очевидно, намного больше float, наводит меня на мысль, что это связано с поведением указателя/массива, приводящим к переполнению.

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

т.е. закрытый член RingBuffer MeasuredMass *_array; становится MeasuredMass _array[25]; и

RingBuffer(unsigned int size){
  _size = size; //забыли вставить эту строку -> div0 -> >1hr bugtracking
  _array = new MeasuredMass[_size];
  for (int i = 0; i < _size; i++){
    _array[i].mg = 0.0;
  }
}

становится

RingBuffer(){
  for (int i = 0; i < _size; i++){
    _array[i].mg = 0.0;
  }
}

Я бы предпочел сохранить возможность установки размера кольцевого буфера во время выполнения (в моем случае он устанавливается на основе количества секунд запрошенных данных), так что может ли кто-нибудь помочь мне выяснить, как мне этого добиться, не сталкиваясь с проблемами переполнения/памяти, которые у меня, похоже, есть?

Спасибо

, 👍1


1 ответ


Лучший ответ:

1

Проблема, скорее всего, здесь:

_array = new MeasuredMass[_size];

Вы выделяете _size * sizeof(MeasuredMass) байт в куче. Если _size слишком большой, объем выделенной памяти становится слишком большим, и ее не удается выделить. Вам следует проверить, является ли _array NULL или нет после new. Если он NULL, то выделение не удалось.

Проблема в том, что выделение кучи ограничено 2 КБ. Это не зависит от фактического объема памяти в чипе. Это ограничение накладывается компилятором (на самом деле библиотеками, поставляемыми вместе с компилятором) и устанавливается значением в скрипте компоновщика. Я много лет агитировал за то, чтобы это было удалено из библиотек, но это всегда оставалось без внимания. Настолько, что я предоставляю «исправленную» версию компилятора в UECIDE, которая снимает это жесткое ограничение.

Лучшее, что вы можете сделать, это не использовать динамическое выделение. Вместо этого используйте статически созданный массив, который вы затем передаете в конструктор .

MeasuredMass massBuffer[25];
RingBuffer massRing(massBuffer, 25);

и:

RingBuffer(MeasuredMass *buf, unsigned int size){
  _size = size; //забыл вставить эту строку ->div0-> >1 час отладки :(
  _array = buf;
  for (int i = 0; i < _size; i++){
    _array[i].mg = 0.0;
  }
}
,