Сбои 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 не имеют способа (насколько я знаю) найти длину сложные массивы указателей, такие как эти.
1 ответ
Лучший ответ:
Вы писали:
// самый крутой 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
- Как читать и записывать EEPROM в ESP8266
- Как сделать выводы Tx и Rx на ESP-8266-01 в выводах GPIO?
- Как навсегда изменить скорость передачи данных ESP8266 (12e)?
- Как заставить 5-вольтовое реле работать с NodeMCU
- Как исправить: Invalid conversion from 'const char*' to 'char*' [-fpermissive]
- ESP8266 не подключается к Wi-Fi
- AT-команда не отвечает на последовательный монитор
- Разница между этими двумя платами NodeMCU?
Где совпадающие звонки на бесплатные?, @Delta_G
Не уверен, что это связано с вашей проблемой, но вы никогда не должны использовать
malloc
в C++. Он выделяет память без фактического создания объекта, что приводит к неопределенному поведению, которое вполне может привести к сбою вашей программы. Вы всегда должны использовать интеллектуальные указатели (std::unique_ptr
,std::shared_pointer
,std::make_unique
,std::make_shared
) для управления динамически выделяемой памятью. В редких случаях, когда вам нужны необработанные выделения, вы можете использоватьnew
/delete
, но никогдаmalloc
/free
., @tttapa