Непоследовательное поведение операторов включения?

Этот вопрос касается шрифтов. Ну, на самом деле, сингулярные включают файлы с константами, которые формируют растровые шрифты или изображения для ЖК- или OLED-дисплеев, таких как этот: https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplayFonts.h. Странно сложная для исследования (но распространенная) проблема возникает, когда кто-либо пытается включить один из этих файлов шрифтов в несколько файлов в приложении.

Даже при наличии включающей защиты повторное использование этого файла .h вызовет странные ошибки компоновщика при переопределении переменной. Оказывается, что любая переменная, фактически объявленная в файле .h, приведет к тому, что она будет создана в программе несколько раз, а затем, когда разные файлы, зависящие от файла font.h, будут связаны, переменные столкнутся. Примером этого может быть здесь: https://stackoverflow.com/a/4936294/11381501, или в моем собственном вопросе некоторое время назад здесь: как создавать большие массивы progmem и не раздражать компоновщика

Проблема в том, что я видел некоторые библиотеки, которые избегают этой ошибки (например, https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplay.h#L64) несмотря на наличие переменных, объявленных в файлах .h. Я даже включил этот файл OLEDDisplay.h (косвенно через файл основной библиотеки для моего экрана) в несколько моих собственных файлов .h в одном проекте, и это не вызвало ошибок компоновщика. Что заставляет эту переменную с глобальной областью действия в файле .h не создавать ошибки компоновщика, когда обычно это делает мой собственный код?

, 👍0

Обсуждение

Защита от включения защищает только от многократного включения в пределах одной единицы перевода. Переменные, объявленные в таких заголовках, должны быть "статическими", но на самом деле должны быть просто объявлены в заголовке как "extern" и определены в их собственном отдельном TU., @Majenko

Вот как я делаю шрифты в своей библиотеке отображения: https://github.com/CariadDisplayLibrary/WhiteRabbit/tree/master/src, @Majenko

@Majenko, существует альтернатива объявлению static против extern (с определением в другом месте) в C ++ 17 и далее, где inline может применяться к ** переменным ** в соответствии с правилом с одним определением, которое едва ли отличается (если вообще отличается) от указанного inline функции., @timemage

@timemage В настоящее время в C ++ существует слишком много "альтернатив". Это превратилось в ужасную запутанную мешанину "функций"..., @Majenko

@Majenko Ну, это решение, которое включает в себя вставку одного ключевого слова. Я подумал, что это было бы полезно узнать., @timemage


1 ответ


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

1

В esp8266-oled-ssd1306 это неправильно, но с примерами библиотеки это сохраняется за счет оптимизации. Другие файлы, в которые включается шрифт, не используют его, поэтому он оптимизирован. В противном случае он был бы продублирован.

OLEDDisplayFonts.h включен в OLEDDisplay.h, который включен в OLEDDisplay.cpp и в SSD1306Wire.h, который включен в скетч. Это создает два экземпляра переменных, определенных в OLEDDisplayFonts.h. Но пример в OLEDDisplay.cpp оптимизирован, потому что этот файл не использует шрифты. Таким образом, для компоновщика существует только один экземпляр переменной.

соответствующая часть из файла .map для примера SSD1306UiDemo.ino

 .irom.text.OLEDDisplayFonts.h.857.11
                0x000000004023b7fd     0x25ab ./SSD1306UiDemo.ino.cpp.o
 .irom.text.OLEDDisplayFonts.h.434.10
                0x000000004023dda8     0x13b9 ./SSD1306UiDemo.ino.cpp.o
 .irom.text.OLEDDisplayFonts.h.10.9
                0x000000004023f161      0xaab ./SSD1306UiDemo.ino.cpp.o
 .irom.text.OLEDDisplayFonts.h.10.9
                0x000000004023fc0c      0xaab ./libraries/esp8266-oled-ssd1306/src/OLEDDisplay.cpp.o
 .irom.text.OLEDDisplayFonts.h.10.9
                0x00000000402406c7      0xaab ./libraries/esp8266-oled-ssd1306/src/OLEDDisplayUi.cpp.o

Правильный способ - поместить переменные в файл cpp или c, и если они должны быть видны из другого файла, то используйте ключевое слово extern в этом файле (обычно поверх включенного файла .h), чтобы объявить, что переменная существует.

,