Unexpected prep

I am a little confused by what I just experienced.

I had a header that had some definitions inside of it wrapped in a preprocessor conditional that I thought would stop it from being included multiple times.

Long story short, this is what I am talking about:

#ifndef GEN3_SX1509_REGISTERS
#define GEN3_SX1509_REGISTERS
byte REG_I_ON[16] = {REG_I_ON_0, REG_I_ON_1, REG_I_ON_2, REG_I_ON_3,
					REG_I_ON_4, REG_I_ON_5, REG_I_ON_6, REG_I_ON_7,
					REG_I_ON_8, REG_I_ON_9, REG_I_ON_10, REG_I_ON_11,
					REG_I_ON_12, REG_I_ON_13, REG_I_ON_14, REG_I_ON_15};
byte REG_T_ON[16] = {REG_T_ON_0, REG_T_ON_1, REG_T_ON_2, REG_T_ON_3,
					REG_T_ON_4, REG_T_ON_5, REG_T_ON_6, REG_T_ON_7,
					REG_T_ON_8, REG_T_ON_9, REG_T_ON_10, REG_T_ON_11,
					REG_T_ON_12, REG_T_ON_13, REG_T_ON_14, REG_T_ON_15};
byte REG_OFF[16] = {REG_OFF_0, REG_OFF_1, REG_OFF_2, REG_OFF_3,
					REG_OFF_4, REG_OFF_5, REG_OFF_6, REG_OFF_7,
					REG_OFF_8, REG_OFF_9, REG_OFF_10, REG_OFF_11,
					REG_OFF_12, REG_OFF_13, REG_OFF_14, REG_OFF_15};

byte REG_T_RISE[16] = {0xFF, 0xFF, 0xFF, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF,
					REG_T_RISE_12, REG_T_RISE_13, REG_T_RISE_14, REG_T_RISE_15};
byte REG_T_FALL[16] = {0xFF, 0xFF, 0xFF, 0xFF,
					0xFF, 0xFF, 0xFF, 0xFF,
					REG_T_FALL_12, REG_T_FALL_13, REG_T_FALL_14, REG_T_FALL_15};

Those definitions were causing a bunch of “multiple definition” errors… but how?

I just moved them to the associated .cpp file, and replaced them with extern declarations, and that fixed the issue

#ifndef GEN3_SX1509_REGISTERS
#define GEN3_SX1509_REGISTERS
extern byte REG_I_ON[16];
extern byte REG_T_ON[16];
extern byte REG_OFF[16];
extern byte REG_T_RISE[16];
extern byte REG_T_FALL[16];

I was hoping someone had an explanation as to why the preprocessor wasn’t stopping the original code from being executed multiple times.

The ifdef / define / endif pattern prevents the content from being included several times into the same compilation unit (i.e. .c or .cpp file), but it does not prevent it from being included into several compilation units.

And since the arrays do not start with extern, they are definitions (as opposed to declarations), i.e. they will define a global symbol and need storage space. When the linker tries to combine the compilation units, you have a conflict (multiple defintions…).

This would not be a problem with declarations. They just inform about the existence of a symbol, but do not define it and have no initialization values.

Your solution is correct:

  • split it into declaration and definitions
  • the declarations (extern... and not initialization values) go into the header file
  • the definitions (no extern and including initialization values) go into a single .cpp file