Беспричинное создание `Serial`, почему?
Возьмем очень простой набросок, любой простой набросок в формате .ino. Скажем, просто светодиодная мигалка, вот такая
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
}
void loop()
{
static bool on;
digitalWrite(LED_BUILTIN, (on = !on) ? HIGH : LOW);
delay(1000);
}
После компиляции в Arduino IDE компилятор сообщает об использовании 10 байтов для глобальных переменных. Это идеально подходит для наших целей.
Теперь добавим к нему дополнительный файл .cpp
. Назовем его foo.cpp
. Файл выглядит следующим образом
#include <Arduino.h>
int foo()
{
return Serial.available();
}
Скомпилируйте все вместе. Теперь компилятор сообщает об использовании 185 байтов для глобальных переменных. Но почему?!
Очевидно, что это дополнительное использование глобальной памяти связано с Serial
. Это связано с тем, что на глобальный объект Serial
ссылается foo
. В исходном скетче Serial
не создается (или не отбрасывается) компоновщиком как неиспользуемый символ. В этом случае на него ссылается foo
, поэтому компоновщик сохраняет его.
Но... Но компоновщик GCC обычно умнее этого. Я ожидаю, что он увидит, что символ foo
нигде не упоминается, поэтому ссылка на Serial
внутри foo
также "не считается" и не должен требовать экземпляра Serial
в конечном коде.
На самом деле, если бы я попытался использовать свой собственный «тяжелый» класс, свою собственную глобальную переменную этого класса и сослаться на нее в foo
(вместо Serial
), GCC будет работать так, как ожидалось: независимо от того, насколько «тяжелой» является моя глобальная переменная, компоновщик GCC отбросит мою глобальную переменную из окончательного кода и сообщит те же 10 байтов для глобальных переменных, как и ожидалось. Использование глобальной памяти останется равным 10, пока я не сошлюсь на foo
откуда-нибудь.
Но Serial
какой-то особенный. Даже «неиспользуемое» упоминание Serial
приводит к его появлению в глобальной памяти. Что в нем такого особенного? Что заставляет его вести себя таким образом?
@AnT, 👍1
Обсуждение2 ответа
Лучший ответ:
Я предполагаю (хотя и не проверял), что это побочный эффект
что тот же файл содержит экземпляр Serial
и
ассоциированные ISR. определение макроса ISR выглядит примерно
вот так (немного упрощенно):
#define ISR(vector) extern "C" \
void __attribute__((signal, used, externally_visible)) vector(void)
Ключевым здесь является атрибут «используемый». Он сообщает компилятору, что следует считать используемым, даже если ни один код не вызывает ISR (по уважительной причине: она вызывается аппаратно).
Когда ваша программа скомпонована, объектный файл foo.o загружается
HardwareSerial0.o. Затем компоновщик видит последовательные ISR, которые
с пометкой «б/у». Они, в свою очередь, используют объект Serial
, который
затем, похоже, также используется.
В моей исходной программе foo.cpp
фактически является частью моей библиотеки Arduino. Насколько мне известно, в процессе сборки Arduino библиотеки компилируются и архивируются в настоящие статические библиотеки. И тогда объектные файлы извлекаются из них, только если они нужны (по стандартному поведению ln
). Таким образом, в моем примере foo.o
не должен быть извлечен из библиотеки и, следовательно, не должен тянуть с собой HardwareSerial0.o
. Однако странное поведение все еще сохраняется: как только библиотека используется скетчем, Serial
немедленно захватывает кусок памяти., @AnT
@AnT, есть ли ваша библиотека dot_a_linkage в library.properties? без него файлы .o помещаются в командную строку компоновщика, @Juraj
@Juraj: Нет, этого не было. И это на самом деле решает проблему сразу., @AnT
Я скомпилировал примеры с microCore/attiny13, который имеет другую реализацию Serial, компилятор ведет себя так, как ожидалось.
Поэтому уменьшим в HardwareSerial.h
#define SERIAL_RX_BUFFER_SIZE 64
to
#define SERIAL_RX_BUFFER_SIZE 0
использует ровно 121 байт (то есть 185-64) то же самое для
#define SERIAL_TX_BUFFER_SIZE 64
to
#define SERIAL_TX_BUFFER_SIZE 0
дает 57 байт (121-64). Используется Защищенный поток
:
unsigned char _rx_buffer[SERIAL_RX_BUFFER_SIZE];
unsigned char _tx_buffer[SERIAL_TX_BUFFER_SIZE];
Я не просматривал все используемые библиотеки, чтобы найти остальные, но я предполагаю, что это глобальные определения (буферы) для глобального объекта Serial. В библиотеке microCore все буферы установлены на 0, вот как я ее нашел.
- Float печатается только 2 десятичных знака после запятой
- Почему запуск последовательного монитора перезапускает скетч?
- Чтение из Serial
- Связь последовательного порта Digispark
- Будет ли .ino-скетч ардуино компилироваться непосредственно на GCC-AVR?
- Как получить правильный последовательный порт?
- Как очистить кучу памяти в esp32
- Поскольку double и float представляют один и тот же тип данных (обычно), что предпочтительнее?
Я уверен, что в реализации Serial есть некоторая «изменчивость»., @Juraj
@Juraj: Какое конкретное появление volatile вы имеете в виду? Компилятор должен сохранять каждое появление *volatile access*. Но если функция никогда не вызывается, нет доступа для сохранения. Если
Serial
делает изменчивый доступ в своем конструкторе, то он всегда должен сохраняться, независимо от того, использует ли клиентский кодSerial
или нет. Это не то поведение, которое мы наблюдаем., @AnTВы проверяли командную строку для компилятора и компоновщика? Может быть какая-то опция, которая останавливает компоновщик от сброса. И вы смотрели в объектные файлы, чтобы увидеть, что использует пространство?
objdump
здесь очень помогает., @the busybeeпри ссылке HardwareSerial.o находится в архиве .a. если бы это было с помощью одного .o в командной строке компоновщика, оно всегда было бы связано. если он находится в .a, он связан только в том случае, если это требуется. это не ответ, а только наводка. Я не знаю, как определяется «требуется», @Juraj