0

I have a rather complex project (PlatformIO, but I've also had this happen in the vanilla IDE) with several nested includes. Let's say that there are three files and a main sketch. The main sketch includes #2 and #3. File #3 includes file #1 and file #2.

If file #2 is a .h file containing something like a font, of the format:

const char foo[] PROGMEM = {0xDE,0xAD,0xBE,0xEF};

Then I get linker errors of multiple definitions of 'foo' in both #3 and main.

Weirdly, the default fonts included in my OLED library are placed in a header file, EXACTLY like how I did it, and yet multiple files including that library (or files that themselves included it) do not cause this issue with the library's header file.

Yes, I do have a working include guard. For some reason it doesn't ever seem to help.

I have been using extern to solve this issue, but it's annoying and I'm not sure that it's proper behavior, either...

Could it have something to do with #include <font.h> vs #include "font.h"? What is the difference? My reference said they mean the same thing, but now I'm not so sure.

3
  • the title of your post does not match what your question is about – jsotola Mar 9 '20 at 6:28
  • What is the filename extension of file #3? – Edgar Bonet Mar 9 '20 at 9:00
  • They are all .h files except for main. The issue is not really related to main, either--this happens any time #2 is included in multiple files. The other .h files have an associated .cpp file, except for #2. – RDragonrydr Mar 9 '20 at 18:38
2

Include Guards only protect against inclusion of a header file multiple times within the same translation unit. This is important. If you include a header file (1) in a source file, then include another header file (2) in the same source file, said header file also including the previous header file (1), then the include guard will perform its function.

If you have two source files and you include the same header in both, then the include guard will do nothing.

If you have a variable or function defined in a header file and you include that header into two different source files, when you link those source files together you will get a multiple definition error, since you have two translation units that have the same thing in them, and the linker doesn't like that.

The only time you should ever define variables or functions in a header file are if they are simple constant types, like a const int or a const char - something where the compiler can replace it verbatim in the code and optimize out the symbol completely (arrays of any kind are not simple), or if the symbol is declared "static", where it will be forced to remain "within" the translation unit and not get exported for linking.

Normally you will only declare your complex constants in the header file, and define them once and once only in a single source file. That way there is only one instance of the symbol in one translation unit.

For example, in the header file you would declare your constant:

extern const char foo[] PROGMEM;

And in your source file define the constant:

#include "foo.h"
const char foo[] PROGMEM = {0xDE,0xAD,0xBE,0xEF};

And in answer to the question "what's the difference between #include <...> and #include "..." - when it comes to Arduino, nothing whatsoever.

Officially it denotes a separation between "system" and "local" headers. That is, headers that are installed in the OS as part of the compiler, and headers that are contained within the project. However there isn't that distinction with Arduino, and the two mean the same thing - just a header.

And finally a note on terminology - a glossary if you will:

  • Translation unit - One compilable source file (and any included headers) that forms a single file of object code to be linked.
  • Declaration (Declare) - The indicating of a symbol's type and any attributes, but not the content of that symbol
  • Definition (Define) - Associating a symbol, whilst declaring it, with the content of that symbol.
  • Symbol - the name of any variable, function, class, etc.
5
  • Okay, so extern IS the right way to do this. Any idea how the OLED library gets away with doing this, though? It defines AND declares that array in its own .h file and causes no errors. And that library gets included in one I use in multiple places (though it itself is only included once...?) so I think that should cause the error... – RDragonrydr Mar 9 '20 at 18:42
  • Can you point to this OLED library? There's no way I can predict what it's doing without being able to see it. – Majenko Mar 9 '20 at 18:43
  • github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/src/… However it works, that file is included by the SSD1306Wire header, which is included by the SSD1306 header I use. – RDragonrydr Mar 10 '20 at 18:34
  • Maybe different versions of the compiler do things in different ways. I know that recently deduplication of static data (like these big arrays) was added, and that the ESP8266's compiler is new enough for that - maybe that has removed the need for it to complain about it being there more than once, since now it can know that they are all the same data anyway. – Majenko Mar 10 '20 at 22:15
  • Strange that it wouldn't do...whatever it was...for mine, though. I'm still learning the intricacies of how files, headers, and placement of things in them affects where they show up, and then this happens, where at least part of the answer seems to be "because it wants to"... :D – RDragonrydr Mar 15 '20 at 3:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.