0

The author of the PinChangeInt library and the EnableInterrupt library states at github:

When the PinChangeInt was in a .h and .cpp file, the Arduino IDE tried to compile it twice. I have a technique that you can use- #define LIBCALL_ENABLEINTERRUPT - that if you put it ahead of your #include in all files but the sketch, then you should be able to use the library without conflicts. If you know how to make it work in another way- remember, this is a library and will not compile in the same directory as the sketch and any other .h/.cpp/.ino files- then I'm all ears.

I came there, because both libraries failed to build with linker errors due to double definitions. So far so good. I split the PinChangeInt library in .h and .cpp myself and it compiled flawlessly. I did not try to split the other library yet because it looks like much work to me.

So now my question(s): Is GreyGnome right with his statement, that library code gets compiled twice when split in code and headers? And if so, what are the consequences? Memory waste? Runtime errors? What about the many other (working) libraries exhibiting separate code and header files?

7
  • I have no clue what he's on about. CPP files only get compiled once. Otherwise the whole Arduino system would fail miserably since most other libraries are split into CPP/H.
    – Majenko
    Aug 30, 2015 at 15:40
  • 1
    My guess is that he didn't split the files properly and ended up with duplicate references.
    – Majenko
    Aug 30, 2015 at 15:50
  • Ahem, so if I have some time again, I'll try to split this library too.
    – Ariser
    Aug 30, 2015 at 16:30
  • As I mentioned in your somewhat related thread the IDE does things in a reasonably understandable way: Classes and objects: how many and which file types I actually need to use them?.
    – Nick Gammon
    Aug 31, 2015 at 7:08
  • @NickGammon: I read that already. But how does this help me with that library? Do I have other means than rewriting it?
    – Ariser
    Aug 31, 2015 at 12:29

2 Answers 2

1

Header files are parsed by the preprocessor and the compiler wherever they are included.

There's nothing weird about that. One can avoid multiple inclusions by using header guards. That's pretty standard too.

I have not looked at the library, but, for example, a typical error is to not declare variables as extern in the header and instead leave the real declaration in the header, which will lead to multiple instances, possibly clashing at link time. Variables that are part of the library API should be declared as extern in the header and the real declaration should be done only in the .C/.CPP file.

Interrupts are an even more special case: the AVR gcc relies on very specific names to identify interrupt handlers and each of them can be present at most once. So there are even more chances for having linker problems, if not declared correctly.

4
  • The problem is, that both libraries lack .c/.cpp files entirely. Hence the real declaration is in the header and it is not possiple to declare it extern. The compiler takes the header guards correctly. But the linker throws the double declaration errors afterwards.
    – Ariser
    Aug 30, 2015 at 18:44
  • Indeed, what should be done is to split the library into .h and .cpp, so that it can work in a sane way. FWIW, though, using PCINT is trivial, if done without the library, assuming that you only care about one specific platform (say ATMega328p). The library bends backwards several times to cope with the different layout of AVR ports across various ATMega chips. But if all you care about is one chip, you can write much simpler code. Aug 30, 2015 at 18:47
  • On my Interrupts I show how you can use pin-change interrupts in about 4 lines of code. My personal view is that if you are targeting a particular processor it is easier to just write those four lines. By the looks of it you have already spent a number of days to "save the effort" of writing those few lines. The problem is, that both libraries lack .c/.cpp files entirely. - so? Split the file into two files. ... it is not possiple to declare it extern - anything is possible.
    – Nick Gammon
    Aug 31, 2015 at 20:41
  • To be fair, the library does more than what is accomplished by those 4 lines. Replicating the same functionality would take a similar amount of code. But it could be done without abusing the header. Aug 31, 2015 at 20:58
0

To elaborate on your comment ~

An easy way to prevent multiple definitions from re-inclusion of the same header file is to ensure it's only defining things once.

e.g. my_header.h

#ifndef __MY_HEADER_H__
#define __MY_HEADER_H__

// some header code.

#endif //#ifndef __MY_HEADER_H__

When the header is first compiled, the definition of __MY_HEADER_H__ gets created by the compiler due to the 2nd line of the file. On subsequent compiles, which are caused by including the same header in more files, the 1st line '#ifndef' conditional skips everything down to the '#endif'.

When you write (or split) C / C++ code, it's common to wrap the class and function definitions (which go in the .h header file) in these compilation control blocks, and then the .c/.cpp file only gets the function bodies.

e.g.: my_rgb.h ~

#ifndef __MY_RGB_H__
#define __MY_RGB_H__

#include <stdint.h>

uint32_t packRGB(uint8_t r, uint8_t g, uint8_t b);

class RGB
{
public:
    RGB(uint8_t r, uint8_t g, uint8_t b);

    void toGreyscale();

private:
    uint8_t red;
    uint8_t green;
    uint8_t blue;
};

#endif //#ifndef __MY_RGB_H__

and my_rgb.cpp

#include "my_rgb.h"

// Note: this would typically be a member function
// but is separate to show a standalone function definition.
uint32_t packRGB(uint8_t r, uint8_t g, uint8_t b)
{
    uint32_t packed;

    packed = (r << 24) | (r << 16) | b;

    return packed;
}

RGB:RGB(uint8_t r, uint8_t g, uint8_t b)
{
    red = r;
    green = g;
    blue = b;
}

void RGB::toGreyscale()
{
    uint8_t grey = 0.2126 * (float)r + 0.7152 * (float)g + 0.0722 * (float)b;
    r = g = b = grey;
}

Including the header file multiple times will give "redefinition" errors at compile time. Including the same function multiple times will give double definitions at link time.

If you stick to this method of laying out .h and .c/.cpp files you will never have a problem with multiple definitions on your own files. (Unless you re-define a library function by accident, etc.)

Of course it's ok (and quite common) to define short little functions in the header/.h file too. (The compiler typically generates these as inline code though.) I'm not trying to explain the complete workings of headers & .C files, just presenting you a method which will always prevent multiple definition problems.

Your Answer

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

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