This question deals with fonts. Well, actually, singular include files with constants that form bitmap fonts or images for LCD or OLED displays, such as this one: https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplayFonts.h. A strangely hard to research (but common) problem occurs when anyone attempts to include one of these font files in multiple files in an application.
Even with an include-guard present, reuse of this .h file will cause strange linker errors about redefinition of a variable. It turns out that any variable actually declared in a .h file will cause it to be created multiple times in the program, and then when the different files depending on the font.h file are linked, the variables collide. An example of it would be here: https://stackoverflow.com/a/4936294/11381501, or in my very own question some time ago here: How to create large progmem arrays and not annoy the linker
The issue is, I've seen some libraries that avoid this error (such as https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/OLEDDisplay.h#L64) despite having variables declared in .h files. I've even included this OLEDDisplay.h file (indirectly via the main library file for my screen) in multiple of my own .h files in one project and it hasn't caused linker errors. What causes this globally-scoped variable in a .h file to not produce linker errors when my own code usually does?
1 Answer 1
The esp8266-oled-ssd1306 has it wrong, but with examples of the library it is saved by optimization. Other files where the font gets included don't use it so it is optimized away. Otherwise it would be duplicated.
OLEDDisplayFonts.h is included in OLEDDisplay.h which is included in OLEDDisplay.cpp and in SSD1306Wire.h which is included in the sketch. This creates two instances of the variables defined in OLEDDisplayFonts.h. But the instance in OLEDDisplay.cpp is optimized away, because that file doesn't use fonts. So for the linker there is only one instance of the variable.
relevant part from .map file for the SSD1306UiDemo.ino example
.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
The right way is to put variables in a cpp or c file and if they should be visible from other file, then use extern
keyword in that file (usually over an included .h file) to declare that the variable exists.
static
but really should be just declared in the header asextern
and defined in their own separate TU.static
vsextern
declaration (with definition elsewhere) in C++17 onwards, whereinline
may be applied to variables following a one-definition-rule scarcely different (if different at all) frominline
specified functions.