Что еще потребляет сегмент данных, о котором сообщает 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);

Я буду тестировать дальше...

Спасибо за разъяснения!

С наилучшими пожеланиями!

, 👍1

Обсуждение

все строковые литералы копируются в SRAM со старыми версиями avr gcc. Макрос F обеспечивает их чтение из флэш-памяти., @Juraj

@Juraj, но когда функция возвращает, выделенное пространство освобождается? кажется, что все мои литералы идут в сегмент данных, который является местом для глобальных переменных., @LeandroIP

извините, должно быть "все строковые литералы копируются в SRAM при загрузке", @Juraj

https://www.arduino.cc/reference/en/language/variables/utilities/progmem/#_the_f_macro, @Juraj


1 ответ


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

2

Большая часть отсутствующего раздела данных, вероятно, состоит из строковых литералов. Когда вы пишете что-то вроде

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