0

A question I have long had regards an interaction between #define statements and a library header include. I've seen several Arduino libraries use this rather convenient configuration system where you can write a #define statement, then include the #include header instruction and the library will change its behavior.

Does anyone know why this does or does not work? My understanding of how #include works is that it copies the file into its place in the new file and that C/C++ code with headers should thus form something of a tree shape. It would thus make sense that any lines of code present before I #include a library would have to be seen by the library when the preprocessor does its thing and copies the #include-d file.

But I've previously been told it doesn't actually work this way (despite actual examples, but by that point I had misplaced the libraries that did it). However, I've finally tracked down an example of someone doing this, and I'd like to know how this behavior works. Is it a feature of Arduino in specific or something?

Here's a library that does it: https://github.com/madpilot/mDNSResolver

5
  • 1
    From my reading of the source code for that library I can't see that it can work as described. The library code (cpp files) are different translation units to the sketch where the #define would be located.
    – Majenko
    Apr 8, 2022 at 22:58
  • Can you explain translation units? I've heard of them, but they seem very nebulous to me.
    – user47164
    Apr 8, 2022 at 23:03
  • When you compile a .c or .cpp file it gathers all the header files contents together and literally does string replacement on the #includes and then the #defines. Then compiles that one big resultant file. That's a translation unit. In general for every cpp or c file you have one translation unit. It's a self contained thing which can't be modified by anything outside. The only exception is in Arduino if you have multiple .ino files in a sketch they get concatenated together into one gigantic translation unit.
    – Majenko
    Apr 8, 2022 at 23:13
  • If I were doing what you describe I'd be using a class with a constructor whose code is in the header file and thus gets recompiled for each translation unit (i.e. within the sketch for example). That constructor would be called with macros you can override from the sketch since the constructor forms part of the same TU as the sketch.
    – Majenko
    Apr 8, 2022 at 23:15
  • the missing define is not missing from your sketch, it's missing from the lib's cpp file, which is it's own thing. one neat trick i've used is to put all the lib code in an H file instead of the cpp file. The result is basically that that H-file code just pasted in-place in the sketch, which means prior defines are available to it. I don't know if there's downsides to it, but it works as I expect...
    – dandavis
    Apr 10, 2022 at 9:38

2 Answers 2

1

The mDNSResolver library README has

You can change a few settings by defining some constants before including the mDNSResolver header file [in the sketch]

I added "in the sketch", because it is where you include mDNSResolver header file.

This will not work because mDNSResolver.cpp, which uses the values of the defines, is compiled without any knowledge of the ino file content.

A deeper explanation would be too long and out of scope for Arduino SE.

-1

In the first few lines of the library .h file you linked, it tests a symbol it has not defined, ESP32 (#if defined(ESP32)) and alters the compilation according to the definedness of that symbol.

Since the compiler input will be one long file with all the #include files already "spliced" inline, you (or a previously included .h file) have the option to pre-define that variable to alter the compilation as this library writer provided.

Update:

@RDragonflydr: I believe you are correct, that the ESP32 symbol is defined by the compiler (or more likely, the makefile) for the target processor. And to clarify my statement that "the compiler input will be one long file", I'm referring to the compilation of each module of your project, not to the entire project at once. Each library will each be compiled, separately, and each of your modules will be compiled separately, and in each of those cases, the compiler input will consist of the module code with all of the #included files "spliced" into place, the #defined symbols and macros substituted, and comments stripped out.

The library code (the .cpp) is not #included so it does not become part of the translation unit. Only the library header file is included. That is common - and necessary - to all environments, and one of the functions of the C or C++ preprocessor. This happens for each module - separately - in a multi-module compilation. The library files will have been

4
  • With a good background in C and C++ since many years, I'm quite stunned. Are you saying that the Arduino build process concatenates all sources of a sketch into one big translation unit? Boy, I wasn't aware of this! Is this also true for default Arduino libraries, or just added libraries? Apr 10, 2022 at 13:10
  • 1
    I'm not quite sure what you're trying to say here. AFAIK the ESP32 define is set by the Arduino core and/or compiler to inform the code what it's running on. That one doesn't have anything to do with the "setting" defines I was asking about.
    – user47164
    Apr 10, 2022 at 15:29
  • I know that Arduino's build process concatenates all the files - is that what allows the "settings" defines to work? I'm not sure if there's a way to get this working in other environments than the Arduino IDE specifically. If this works at all, is it only in Arduino and it's fragile if you use a different IDE to code for Arduino?
    – user47164
    Apr 10, 2022 at 15:31
  • @thebusybee, only the ino files in the sketch are concatenated to one cpp file, not the C, C++, S files (of course)
    – Juraj
    Apr 12, 2022 at 19:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.