Как создавать большие массивы программ и не раздражать компоновщика
У меня есть довольно сложный проект (PlatformIO, но у меня также было это в vanilla IDE) с несколькими вложенными включениями. Допустим, есть три файла и основной скетч. Основной скетч включает №2 и №3. Файл №3 включает файл №1 и файл №2.
Если файл №2 представляет собой файл .h, содержащий что-то вроде шрифта, в формате:
const char foo[] PROGMEM = {0xDE,0xAD,0xBE,0xEF};
Затем я получаю ошибки компоновщика несколько определений 'foo' как в #3, так и в main.
Как ни странно, шрифты по умолчанию, включенные в мою библиотеку OLED, помещаются в заголовочный файл, ТОЧНО так, как это сделал я, и все же несколько файлов, включающих эту библиотеку (или файлы, которые сами включали ее), не вызывают эту проблему с библиотекой. заголовочный файл.
Да, у меня есть работающая защита включения. По какой-то причине это никогда не помогает.
Я использовал extern
для решения этой проблемы, но это раздражает, и я не уверен, что это правильное поведение...
Может ли это быть как-то связано с #include <font.h>
и #include "font.h"
? В чем разница? В моей ссылке сказано, что они означают одно и то же, но теперь я не уверен.
@, 👍0
Обсуждение1 ответ
Лучший ответ:
Защита включения защищает только от многократного включения файла заголовка в одну и ту же единицу перевода. Это важно. Если вы включаете заголовочный файл (1) в исходный файл, а затем включаете другой заголовочный файл (2) в тот же исходный файл, причем указанный заголовочный файл также включает предыдущий заголовочный файл (1), то защита включения выполнит свою функцию.
Если у вас есть два исходных файла и вы включаете один и тот же заголовок в оба, то защита включения ничего не сделает.
Если у вас есть переменная или функция, определенная в файле заголовка, и вы включаете этот заголовок в два разных исходных файла, при связывании этих исходных файлов вы получите ошибку множественного определения, так как у вас есть две единицы перевода, которые имеют в них одно и то же, и компоновщику это не нравится.
Определять переменные или функции в заголовочном файле следует только в том случае, если они являются типами простых констант, например const int
или const char.
- что-то, где компилятор может дословно заменить его в коде и полностью оптимизировать символ (массивы любого типа не являются простыми), или если символ объявлен как "статический", где он будет вынужден оставаться «внутри» единицы перевода и не будет экспортироваться для связывания.
Обычно вы будете объявлять свои комплексные константы только в заголовочном файле и определять их один раз и только один раз в одном исходном файле. Таким образом, в одной единице перевода будет только один экземпляр символа.
Например, в заголовочном файле вы должны объявить свою константу:
extern const char foo[] PROGMEM;
И в исходном файле определите константу:
#include "foo.h"
const char foo[] PROGMEM = {0xDE,0xAD,0xBE,0xEF};
И в ответ на вопрос "в чем разница между #include <...>
и #include "..."
- когда речь идет о Arduino, ничего подобного.
Официально это означает разделение между "системными" и "локальными" заголовками. То есть заголовки, которые устанавливаются в ОС как часть компилятора, и заголовки, которые содержатся внутри проекта. Однако в случае с Arduino такого различия нет, и они означают одно и то же — просто заголовок.
И, наконец, примечание по терминологии — глоссарий, если хотите:
- Единица перевода — один компилируемый исходный файл (и все включенные заголовки), формирующий единый файл объектного кода, который необходимо связать.
- Объявление (Declare) — указание типа символа и любых атрибутов, но не содержимого этого символа.
- Определение (Define) — связывание символа при его объявлении с содержимым этого символа.
- Символ – имя любой переменной, функции, класса и т. д.
Итак, extern — правильный способ сделать это. Есть идеи, как OLED-библиотеке это удается? Он определяет И объявляет этот массив в своем собственном файле .h и не вызывает ошибок. И эта библиотека включается в ту, которую я использую в нескольких местах (хотя сама она включается только один раз...?), поэтому я _думаю_, что это должно вызвать ошибку..., @RDragonrydr
Можете ли вы указать на эту библиотеку OLED? Я никак не могу предсказать, что он делает, не видя его., @Majenko
https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplayFonts.h Однако это работает, этот файл включен в заголовок SSD1306Wire, который включен в заголовок SSD1306, который я использую., @RDragonrydr
Возможно, разные версии компилятора делают что-то по-разному. Я знаю, что недавно была добавлена дедупликация статических данных (таких как эти большие массивы) и что компилятор ESP8266 достаточно новый для этого — возможно, это избавило его от необходимости жаловаться на то, что он был там более одного раза, поскольку теперь он может знайте, что все они в любом случае являются одними и теми же данными., @Majenko
Странно, что это не помогло... что бы это ни было... для меня. Я все еще изучаю тонкости того, как файлы, заголовки и размещение в них вещей влияют на то, где они появляются, а затем происходит это, где, по крайней мере, часть ответа кажется «потому что он хочет»... : Д, @RDragonrydr
- C++ против языка Arduino?
- ошибка: ожидаемое первичное выражение перед токеном ','
- Ввести идентификатор чипа ESP32 в строковую переменную (новичок в Arduino/C++)
- Передача функции-члена класса в качестве аргумента
- Улучшенное циклическое переключение цветов RGB.
- Какие есть другие IDE для Arduino?
- Несовместимые типы при назначении «uint8_t {aka unsigned char}» на «uint8_t [1] {aka unsigned char [1]}»
- Как преобразовать массив символов в строку в arduino?
название вашего сообщения не соответствует тому, о чем ваш вопрос, @jsotola
Какое расширение у файла №3?, @Edgar Bonet
Это все файлы .h, кроме основного. Проблема на самом деле не связана с main — это происходит каждый раз, когда # 2 включается в несколько файлов. Другие файлы .h имеют связанный файл .cpp, за исключением #2., @RDragonrydr