Сбои ESP8266, связанные с malloc/calloc («segfaults»)

Это своего рода продолжение этой проблемы. Получив намеки на то, что проблема связана с плохой памятью, я еще раз проверил весь свой код выделения кучи и нашел malloc, который не выделял достаточно памяти для массива. После исправления этого и возни с calloc, который может быть более безопасным для вещей, связанных с массивами, я думаю, что выявил сами фактические ошибки программирования. Сообщение о сбое, которое я получаю сейчас:

No poison after block at: 0x3fff0fb8, actual data: 0x84 0xf 0xff 0x3f

User exception (panic/abort/assert)
Panic umm_malloc.cpp:422 umm_malloc_core

что мне кажется чем-то вроде ошибки ESP. Код, в котором это происходит согласно трассировке стека, следующий:

Menu* createMenuStructure(ace_time::TimeZone* mainTZ) {
  // создаем массив всех циферблатов, т.е. указатель на массив указателей на функции
  void (**allClockFaces)(T_DISPLAY*, ace_time::ZonedDateTime*);
  // самый крутой malloc в мире; неопределенное поведение? Надеюсь нет.
  allClockFaces = (void (**)(T_DISPLAY*, ace_time::ZonedDateTime*)) calloc(DESIGN_MENU_SIZE, sizeof(*allClockFaces));
  // инициализируем массив
  allClockFaces[0] = &basicDigitalCF;
  allClockFaces[1] = &digitalWithSecondsCF;
  allClockFaces[2] = &basicAnalogCF;
  allClockFaces[3] = &binaryCF;
  allClockFaces[4] = &fullDayBinaryCF;
  ClockFaceSelectMenu* cfMenu = (ClockFaceSelectMenu*) malloc(sizeof(ClockFaceSelectMenu));
  cfMenu = new ClockFaceSelectMenu((char**)design_menu, (size_t)DESIGN_MENU_SIZE, allClockFaces);

  // меню выбора часового пояса
  TimeZoneSelectMenu* tzMenu = (TimeZoneSelectMenu*) malloc(sizeof(TimeZoneSelectMenu));
  tzMenu = new TimeZoneSelectMenu();

  Menu** allMenus = (Menu**) calloc(3, sizeof(*allMenus));
  // инициализируем первый слой подменю
  allMenus[0] = cfMenu;
  allMenus[3] = tzMenu;
  
  OptionsMenu* mainMenu = (OptionsMenu*) malloc(sizeof(OptionsMenu));
  mainMenu = new OptionsMenu((char**)main_menu, (size_t)MAIN_MENU_SIZE, allMenus);
  
  // сейчас вручную установите родителем подменю главное меню. Когда все меню будут завершены, главное меню сделает это автоматически.
  allMenus[0]->parent = mainMenu;
  allMenus[3]->parent = mainMenu;
  
  ClockMenu* clk = (ClockMenu*) malloc(sizeof(ClockMenu));
  // timeClient — глобальная переменная NTPClient
  clk = new ClockMenu(&timeClient, mainTZ, (Menu*)mainMenu);
  
  return clk;
}

Все имена, содержащие Menu, являются классами, производными от этого базового класса, а верхняя пара строк выделяет массив указателей на функции. И просто для ясности: этот код отлично работал в течение последних нескольких дней разработки, пока я не внес некоторые изменения в функциональность SD-карты, которые вызывали постоянные сбои.

Я не очень привык работать с ручным управлением памятью. Уместен ли такой способ размещения, и если да, то правилен ли он? Что я делаю не так, или ошибка кроется где-то еще в проекте (~1500sloc)?

Редактировать: хотя проблема уже решена, благодаря утвержденному ответу я хотел уточнить, что все константы, используемые при выделении массива, верны и соответствуют длине массива строк. Например, main_menu — это строковый массив длиной 5, а MAIN_MENU_SIZE — это константа, определенная рядом с определением массива, которое также определено равным 5. Это, конечно, необходимо, поскольку массивы C не имеют способа (насколько я знаю) найти длину сложные массивы указателей, такие как эти.

, 👍2

Обсуждение

Где совпадающие звонки на бесплатные?, @Delta_G

Не уверен, что это связано с вашей проблемой, но вы никогда не должны использовать malloc в C++. Он выделяет память без фактического создания объекта, что приводит к неопределенному поведению, которое вполне может привести к сбою вашей программы. Вы всегда должны использовать интеллектуальные указатели (std::unique_ptr, std::shared_pointer, std::make_unique, std::make_shared) для управления динамически выделяемой памятью. В редких случаях, когда вам нужны необработанные выделения, вы можете использовать new/delete, но никогда malloc/free., @tttapa


1 ответ


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

0

Вы писали:

// самый крутой malloc в мире

Прикольно, правда. ;-) Мне нравится его извращенная красота, но не всем делает. Некоторые могут посоветовать вам использовать typedef для этого типа функции.

allClockFaces[4] = &fullDayBinaryCF;

Вы уверены, что DESIGN_MENU_SIZE строго больше 4? Может быть, Здесь хорошо было бы использовать assert().

ClockFaceSelectMenu* cfMenu = (ClockFaceSelectMenu*) malloc(...);
cfMenu = new ClockFaceSelectMenu(...);

Вы присваиваете значение cfMenu дважды подряд, что означает значение, назначенное первым, теряется. Это утечка памяти. Вы не должны malloc() здесь, так как new заботится о выделении памяти до инициализация объекта. Эта ошибка повторяется несколько раз в этом функция.

allMenus[3] = tzMenu;

allMenus имеет длину 3: допустимые индексы находятся в диапазоне от 0 до 2. Это доступ к массиву за пределами границ и вероятная причина наблюдаемых ошибок.

,

Хорошо, поведение new было не совсем понятно мне, особенно потому, что Arduino не совсем стандартный C++. Спасибо, что прояснили для меня все и намекнули на typedef., @kleines filmröllchen

Насчет «_Arduino — это не совсем стандартный C++_»: это слегка препроцессированный C++. Arduino.h подключается неявно, для вас добавляются прототипы функций, а затем ваш код передается обычному компилятору C++ (а именно avr-gcc)., @Edgar Bonet

Но какой стандарт С++? Я обнаружил, что некоторые функциональные возможности C++, которые я исследовал в Интернете, не будут приняты avr-gcc или должны быть изменены. Я знал о неявных прототипах функций, которых я все равно избегаю, и просто добавил void setup(); void loop(); в начале моих основных файлов., @kleines filmröllchen

Это C++11 (platform.txt помещает «-std=gnu++11» в compiler.cpp.flags) без исключений, RTTI или STL. Поддерживаемые вещи (например, «новые») обычно работают так, как ожидалось., @Edgar Bonet