4

I'm in the process of moving code written to be compiled for one chip onto another chip.

One issue that's come up is a multitude of multiple definition errors. Some of which appear to be due to the linker for the first chip letting me be lazy with declaring variables extern when they are to be used across multiple source files. I didn't use extern at all previously (declare and define a variable in something.h, use it in something.cpp and other source files that included something.h) and it compiled and linked perfectly well.

I've fixed these issues well enough, I believe: now my variables that are shared have this pattern:

Something.h

extern int foo;

Something.cpp

int foo = 0;

//using foo to do stuff

Main.cpp

#include "Something.h"

//using foo to do stuff

All good.

Here's the bit I don't understand, and can't seem to find any answers to here or on Google. I've noticed the same multiple definition errors being caused by variables that are declared and defined in Something.h and used only in Something.cpp.

Something.h has an include guard, so I don't think it's due to Something.h being included multiple times somewhere in my program.

The error goes away if I declare it as extern and define it in the cpp file, but this feels wrong to me. I believe the extern is not needed to link a variable between a Something.h and Something.cpp.

Any advice would be greatly appreciated, I'd really like to understand what I'm missing here.

(I'm compiling for ESP32 using the Arduino IDE by the way.)

JRVeale
  • 394
  • 2
  • 10
  • 1
    Is `Something.h` included _only_ in `Something.cpp` or maybe also in `SomeOtherThing.cpp` ? (Think about what happens when you compile your different translation units that use the same header.) – Mat Mar 19 '19 at 10:04
  • I think I might be starting to understand what you mean. I assumed that using an include guard would prevent this being an issue, but are you suggesting that because each file is compiled separately before being linked, the include guard doesn't help at all? I'm not sure I understand why that would be the case, as then I can't see the situation where the include guard *does* work? – JRVeale Mar 19 '19 at 10:17
  • 1
    The include guard works per source file with `#include "Something.h"` inside. The guard would be effective only if you have several (probably indirect) `#include "Something.h"` in one file. Each source creates an instance of the same variable. – harper Mar 19 '19 at 10:17
  • Ah, of course, thanks! – JRVeale Mar 19 '19 at 10:45

3 Answers3

8

If you declare your variable in the header file:

#ifndef GLOBAL_H
#define GLOBAL_H

int foo = 0;

#endif

In every include of your header file or translation unit, a new instance of your integer is created. As you mentioned, to avoid this, you need to declare the item as "extern" in the header file and initialize it in the implementation file:

// .h
extern int foo;

// .cpp
int foo = 0

A more C++ way to do that can be something like this:

#ifndef GLOBAL_H
#define GLOBAL_H

struct Global {
    static int foo;
};
#endif

And in your cpp file:

#include "variables.h"

int Global::foo = 0;

C++17 fixes this problem with inline variables, so you can do:

#ifndef GLOBAL_H
#define GLOBAL_H

inline int foo = 0;

#endif

See How do inline variables work? for more information.

mohabouje
  • 3,867
  • 2
  • 14
  • 28
  • Thanks very much, especially for suggestions on proper style. You're "more C++ way" convinces me of the feeling I've had for a while now that this header needs fewer global variables and more encapsulation in a class – JRVeale Mar 19 '19 at 10:44
  • What about this if I'm coding in C and I want to define an array with error message strings? It is natural for me to keep string definition just below enumeration definition, and the latter must be kept in h file in order to be used outside. Is my way a bad practice? What is the best? – MaxC Nov 18 '20 at 20:44
4

The error goes away if I declare it as extern and define it in the cpp file,

The issue is that even when include guarded etc. the variable gets created once in each compilation unit - but since it is global it is pointing to same variable.

To overcome this is issue you need to either create it in anon. namespace

Something.h

namespace {
  int foo = 0;
}

Or, use the static keyword

Something.h

 static int foo = 0;

Both will create a different variable in each compilation unit.

darune
  • 10,480
  • 2
  • 24
  • 62
0

in C++ 17 you can resolve this issue by inline variables. Inline variables avoid the duplication of variables defined in header files. The variables defined in the header file will be treated as initialized in a cpp file. They won't be duplicated. So can be directly included the code.