Как исправить код утечки памяти в ESP8266/NodeMCU, вызванный концентрацией строк?
У меня есть следующий код в loop() в NodeMCU.
Эта часть перед установкой():
String serial_data_read = ""; // для входящих последовательных строковых данных
String serial_data = ""; // для входящих последовательных строковых данных
Эта часть находится в цикле():
if ((last_weight_time < millis() - 1000)) {
last_weight_time = millis();
serial_data_read = "";
Serial.print("P");
while(Serial.available()) {
character = Serial.read();
serial_data_read.concat(character);
}
serial_data = serial_data_read;
}
Я не очень хорошо разбираюсь в программировании на языке Си, но думаю, что это как-то связано с утечкой памяти и ее окончательным исчерпанием. Он работает около 20 минут, а затем просто останавливается. Когда я вручную перезапускаю NodeMCU, он снова работает нормально.
Я был бы очень благодарен за любую помощь, как переписать мой код, чтобы он не пропускал память. Большое спасибо.
@Frodik, 👍0
Обсуждение3 ответа
Лучший ответ:
Чтобы написать код, который не вызовет утечку памяти, вам сначала нужно понять, где и что вызвало утечку памяти. Утечка памяти в данном конкретном случае вызвана строкой
serial_data_read.concat(character);
Когда вы создаете экземпляр строкового объекта с помощью String serial_data_read = ""
, класс Arduino String создает выделение памяти, используя динамическое выделение памяти в куче, и, поскольку вы инициализировали его с помощью"", он, следовательно, создает минимальный размер выделения. Все это хорошо, если вы знаете, как создается динамическая память и как она будет использоваться.
Проблема появилась, когда вы начинаете делать конкатенацию строк с линией serial_data_read.concat(character)
, оригинал распределения для глобальных переменных serial_data_read
уже не хватает места для составной переменной, так это создать временную переменную в куче, а не конкатенации, а затем в конечном итоге поместить результат обратно к недавно выделенной памяти для serial_data_read
в куче, оставляя отверстие в кучи для оригинального serial_data_read
переменной (и переменную Temp).
Хуже всего то, что это происходит для каждой итерации каждые 16 итераций(см. Комментарий Эдгара Бонета ниже) цикла while. Теоретически старый serial_data_read
до конкатенация должна быть бесплатной-в какой-то момент (например, если ваш цикл while в функции части кучи может быть бесплатным при выходе из функции), он тем не менее не стоит быть так, потому что вновь созданной serial_data_read
оставаться на вершине кучи, чтобы быть свободной памяти, что блокирование высвобождения(представьте себе кусок сыра с большим количеством дырочек на нем), так что памяти меньше и меньше на каждой итерации.
Краткосрочное исправление - не выполняйте связывание строк в цикле
Класс String не всегда является злым, если вы знаете, что происходит и как его использовать. Одно из быстрых решений вашей проблемы заключается в том, чтобы просто не выполнять связывание строк в цикле. И если вы используете его в функции, сохраняйте его локальным. Это сведет к минимуму фрагментацию кучи.
if ((last_weight_time < millis() - 1000)) {
last_weight_time = millis();
if (Serial.available() > 0) {
String serial_data_read = Serial.readStringUntil('\n');
}
}
Кстати, вы можете добавить ESP.getFreeHeap()
в свой цикл (), чтобы проверить, есть ли еще утечка памяти.
Долгосрочное исправление - Не используйте класс String
Для лучшего исправления, и особенно для кого-то, кто не знаком с программированием или C/C++, не используйте класс String и узнайте, как использовать строку c и массив. Вот версия без использования класса String.
length = 80;
char buffer[length] = {0};
if ((last_weight_time < millis() - 1000)) {
last_weight_time = millis();
while(Serial.available()) {
Serial.readBytesUntil('\n', buffer, length);
}
}
Другие ресурсы, о которых можно узнать
https://www.cplusplus.com/reference/cstring/ все функции для управления строками c и массивами для замены класса строк Arduino.
https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
Re “_[распределение кучи] происходит для каждой итерации цикла while”: на ESP8266 это происходит один раз каждые 16 итераций, так как выделенный размер увеличивается кратно 16 байтам. Повторите “ _ если ваш цикл while находится в функции, часть кучи может освободиться при выходе функции_”: когда concat()
не сможет вырастить строку на месте, он выделит новый буфер, скопирует существующие данные и освободит предыдущий буфер _ прямо_. Однако это не отменяет вашу точку зрения о риске фрагментации памяти., @Edgar Bonet
Короткий ответ: вообще не используйте класс String. Вам нужно ознакомиться с использованием строк C (которые являются просто массивами символов, заканчивающимися на нуль. Существуют функции библиотеки C для управления строками C. Вы захотите избежать printf, scanf и связанных с ними функций, так как библиотека, которая их поддерживает, довольно большая
Основная идея состоит в том, чтобы использовать статически выделенные строки C в качестве буферов. Определите наибольшее количество символов, которое вам нужно сохранить, и создайте статический массив символов, по крайней мере, на один символ больше (для завершающего значения null), и используйте его для сбора выходных данных, которые вы создаете.
printf() и друзья не оказывают большого влияния на пространство данных и не вызывают его фрагментации. Они занимают больше места в коде, чем Serial.print (), но большинство из нас не пишут скетчи, которые превышают лимит места в коде., @JRobert
@Duncan C - Большое вам спасибо за ваш ответ. Не могли бы вы, пожалуйста, добавить пример кода, как мой код должен быть переписан ?, @Frodik
Что сказал @JRobert. Избегать строки-хороший совет, избегать printf, scanf-плохой совет в данном случае; они не вызывают проблем с кучей, и в этом заключается эта проблема., @romkey
Кроме того, ESP8266 имеет достаточно места для кода (flash) даже на небольшом варианте. На небольших микросхемах AVR семейство printf использует урезанную версию с удаленной поддержкой плавающей запятой для экономии места., @Majenko
Приятно узнать о сокращенной версии printf/scanf. К оператору: не обращайте внимания на эту часть моего ответа, @Duncan C
даже при включении поддержки printf с плавающей запятой с помощью флагов компоновщика это влияет только на размер флэш-памяти, добавляя около 1500 байт, это не влияет на использование SRAM., @hcheung
SafeString-отличный способ пойти.. только один экземпляр каждого SafeString может когда-либо существовать, и u может объявить длину каждого SafeString..
- Можно ли определить размер стека по свободной куче?
- Помогите разграничить и прочитать конкретное содержимое входящей строки HTTP
- Как заставить 5-вольтовое реле работать с NodeMCU
- ESP8266 не подключается к Wi-Fi
- Разница между этими двумя платами NodeMCU?
- NodeMCU - использовать кнопку flash в качестве входного сигнала в loop()
- Как определить размер Flash?
- Как изменить имя модуля ESP8266-12E по умолчанию
Просто не используйте массивы "String`, а массивы "char", @chrisl
https://majenko.co.uk/blog/evils-arduino-strings, @Majenko
Re
if ((last_weight_time < millis() - 1000))
: This can fail badly iflast_weight_time
is right before amillis()
rollover event. Use instead the idiomif (millis() - last_weight_time >1000)
, что является безопасным опрокидыванием., @Edgar Bonet