Что еще потребляет сегмент данных, о котором сообщает avr-size?
Я столкнулся с проблемой нехватки памяти в Arduino. Собираю большой скетч для arduino mega 2560.
При анализе файла .elf инструмент avr-size дает:
text data bss dec hex filename
68524 4392 2560 75476 126d4 C:\Users\LEAN...
А avr-nm (avr-nm doc) показывает только переменные с t, T, b, d и B. Ниже показано, как выполняется команда (результаты сохраняются в файле с именем test.txt).
avr-nm -a -td -C -l --size-sort -r x.elf >> test.txt
Затем я выполняю суммирование, чтобы подтвердить результат размера avr, используя этот скрипт Python:
f = open("test.txt", "r")
lines = f.readlines()
f.close()
su = 0
dsum = 0
bsum = 0
tsum = 0
gsum = 0
for line in lines:
#print(line, end="")
s = line.split()
try:
val = int(s[0])
su = su + val
if(s[1].lower() == 'b'):
bsum = bsum + val
if(s[1].lower() == 'd'):
print(line)
dsum = dsum + val
if(s[1].lower() == 't'):
tsum = tsum + val
if(s[1].lower() == 'g'):
gsum = gsum + val
except:
print("ERR: ",s[0])
print("\n\nSIZE: ", su)
print("SIZE d/D: ", dsum)
print("SIZE b/B: ", bsum)
print("SIZE t/T: ", tsum)
print("SIZE g/G: ", gsum)
и результат суммы каждого типа var:
SIZE: 71015
SIZE d/D: 135
SIZE b/B: 2560
SIZE t/T: 68320
SIZE g/G: 0
Вопрос: Здесь d/D sum составляет 135 байт. Итак, почему сегмент данных, о котором сообщает avr-size, составляет 4392 байта? как узнать, что занимает это место?
С уважением.
ОБНОВЛЕНО
Должно быть, это шутка!
У меня в коде много подобного:
Serial.print("QTDE REG: ");
Просто меняю для этого:
Serial.print(F("QTDE REG: "));
Я уменьшил размер .data на 10!
Итак, таким образом Serial.print("QTDE REG: ");
"QTDE REG: " хранится как глобальная переменная?? Я думал, что компилятор сохранит локально.
Обновление 2
У меня есть предварительный запуск avr-objdump -s -j .data x.elf
, предложенный Эдгаром ниже. Теперь я вижу, что все мои строковые литералы находятся в данных! Например:
void myFunction(){
...
sprintf(buf, "{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit\":\"C\"},", (int)settings.treg);
...
}
avr-objdump -s -j .data x.elf
показывает:
800660 2c007b22 6e616d65 223a2249 6e746572 ,.{"name":"Inter
800670 76616c6f 20646520 52656769 7374726f valo de Registro
800680 73222c22 76616c75 65223a22 2564222c s","value":"%d",
800690 2022756e 6974223a 2243227d 2c007b22 "unit":"C"},.{"
ОБНОВЛЕНО 3
Как указано, все строковые литералы сохраняются в (SRAM). Итак, проблема в том, что у меня много строковых литералов, разбросанных по разным функциям. Как сохранить это количество SRAM? Обратите внимание, что использование F() для sprintf приводит к ошибке компиляции.
Есть ли способ поместить литерал в стек (освободить память при возврате функции), а не в глобальную область? Является ли F() единственным решением?
============ РЕШЕНИЕ ============
На данный момент я нашел такое решение: используйте sprintf_P
с PSTR(...)
так же, как F(...)
:
sprintf_P(buf, PSTR("{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit\":\"C\"},"), (int)settings.treg);
Я буду тестировать дальше...
Спасибо за разъяснения!
С наилучшими пожеланиями!
@LeandroIP, 👍1
Обсуждение1 ответ
Лучший ответ:
Большая часть отсутствующего раздела данных, вероятно, состоит из строковых литералов. Когда вы пишете что-то вроде
Serial.println("Hello, World!");
строка "Hello, World!"
реализуется компилятором как
анонимный массив символов, и адрес этого массива
предоставляется методу println()
. Поскольку массив анонимный, он
не отображается в выводе avr-nm
. Вы можете проверить, что это
случай, просмотрев фактическое содержание раздела:
avr-objdump -s -j .data x.elf
И этого можно избежать, заключив строки в макрос F()
:
Serial.println(F("Hello, World!"));
Это приводит к сохранению строки во flash и передаче
флеш-адрес в метод println()
. Обратите внимание, что на самом деле это
другой, перегруженный метод, который знает, как получить символ
данные из флэш-памяти.
Правка: Добавление пояснений.
Вся эта сложность в сравнении флэш-памяти с оперативной памятью связана с тем, что AVR использует гарвардскую архитектуру, где оперативная память и флэш-память в разных адресных пространствах, доступ к которым осуществляется через разные шины памяти используя различные машинные инструкции. В отличие от C, C++ язык не имеет понятия об адресных пространствах, и компилятор просто предполагает, что любой указатель на функцию содержит адрес флэш-памяти, тогда как любой указатель на данные содержит адрес ОЗУ.
По правилам языка строковый литерал имеет тип
const char *
и функция, которая должна принимать строку
литерал должен принимать параметр const char *
. Так как это указатели
к данным, когда вы их разыменовываете, компилятор выдает машину
инструкции, подходящие для чтения ОЗУ. Вот почему
компилятор помещает все строковые литералы в раздел .data флэш-памяти: at
запуск программы, среда выполнения C копирует их в раздел .data файла
ОЗУ, что делает их доступными для этих машинных инструкций.
Существующее решение, позволяющее избежать этой бесполезной копии, включает в себя множество
макросы препроцессора, такие как F()
, PSTR()
, PROGMEM
,
pgm_read_byte()
... которые расширяются до расширений компилятора или встроенных
сборка. Они позволяют передавать флэш-адреса строки
литералы, замаскированные под обычные указатели данных. Эти указатели никогда не должны
разыменовываться обычными операторами языка (*
и
[]
), но только с помощью макросов pgm_read_*()
. И они должны только
быть переданы функциям, которые ожидают такие указатели, например
sprintf_P()
или Print::println(const __FlashStringHelper *)
, что
println()
, который вызывается при использовании макроса F()
.
Есть ли способ поместить литерал в стек
Да, есть, но это должно быть сделано явным образом:
PROGMEM const char greeting[] = "Hello, World!";
void say_hello() {
char stack_copy[strlen_P(greeting)+1];
strcpy_P(stack_copy, greeting);
Serial.println(stack_copy);
}
Как вы уже заметили, желательно не помещать строки в
оперативной памяти и вместо этого используйте такие функции, как sprintf_P
, которые ожидают
строки на основе flash. Однако, если вам нужно использовать библиотечную функцию, которая
ожидает строку на основе ОЗУ, вы можете использовать этот трюк, чтобы избежать постоянного
фиксируя его в ОЗУ.
Использование F() в println экономит мне около 100 байт. Выполняя avr-objdump, как было предложено, я вижу строку `
sprintf(buf, "{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit \":\"C\"},", (int)settings.treg);`
в нем. Все строки хранятся в этой области?, @LeandroIP
@LeandroIP: все строковые литералы, если только не используется макрос F()
или, что то же самое, PSTR()
., @Edgar Bonet
Спасибо @Edgar, PSTR() решает проблему с sprintf_P. Подробнее см. РЕШЕНИЕ в ответе., @LeandroIP
- Есть ли ограничения на размер массива в Arduino Mega 2560?
- Как получить тип данных переменной?
- Преобразование в Unix Timestamp и обратно
- Получить доступ к EEPROM ATtiny с помощью кода Arduino?
- Записать во флэш-память с помощью PROGMEM
- Глобальные переменные занимают много места в динамической памяти.
- Невозможно создать массив типа const char*
- ардуино - миллисекунды ()
все строковые литералы копируются в SRAM со старыми версиями avr gcc. Макрос F обеспечивает их чтение из флэш-памяти., @Juraj
@Juraj, но когда функция возвращает, выделенное пространство освобождается? кажется, что все мои литералы идут в сегмент данных, который является местом для глобальных переменных., @LeandroIP
извините, должно быть "все строковые литералы копируются в SRAM при загрузке", @Juraj
https://www.arduino.cc/reference/en/language/variables/utilities/progmem/#_the_f_macro, @Juraj