Preprocessor's puzzling behavior

Hi all,

I got stuck for a few hours trying to make my code compile on the boron OS v4.0.0, and as the error message wouldn’t go away, I ended up stripping away almost the entirety of my code. That is until I somewhat figured it out, but this case is so bizarre and confusing that I’d love to hear an explanation for it.

Anyway here’s how it goes, let’s start with a very basic .ino file:

// Try #1
PRODUCT_VERSION(1)

typedef uint8_t myType_t;

void mySimpleFunction(myType_t x) {
}

void setup() {
}

void loop() {
}

Nothing too complicated, right? Well, surprisingly it does not compile because apparently “myType_t is not declared in this scope”.

Okay then, let’s try something else. Let’s say we really want myType_t to have a uint8_t, so why not put it in a struct instead? Like so:

// Try #2
PRODUCT_VERSION(1)

typedef struct {
    uint8_t a;
} myType_t;

void mySimpleFunction(myType_t x) {
}

void setup() {
}

void loop() {
}

Bingo, now myType_t seems to be declared in this scope and it compiles, great work indeed.

Now, what if we want to have this typedef inside a header file? Let’s make a simple header file “some_header.h” (the include guards won’t change the outcome):

#ifndef SOME_HEADER_H
#define SOME_HEADER_H

typedef struct {
    uint8_t a;
} myType_t;

#endif

And our main .ino file:

// Try #3
PRODUCT_VERSION(1)

#include "some_header.h"

void mySimpleFunction(myType_t x) {
}

void setup() {
}

void loop() {
}

You probably guessed it, that one does not compile. I thought an include was just equivalent to a copy/paste, but here it cannot be the case, otherwise, Try #2 and #3 would give the same result.

And last but not least, if you swap the first and second lines, you get:

// Try #4
#include "some_header.h"

PRODUCT_VERSION(1)

void mySimpleFunction(myType_t x) {
}

void setup() {
}

void loop() {
}

And this code compiles (which is ironic because the docs tell us to include the product version “at the top of the firmware source code”).

By chance I came across this sentence in the docs:

If you are getting unexpected errors when compiling valid code, it could be the preprocessor causing issues in your code.

And surely enough, adding

#pragma PARTICLE_NO_PREPROCESSOR
#include "Particle.h"

at the beginning of each of these codes seems to fix the issue. But at this point, after spending so long trying to figure it out, I feel (and probably you as well if you read this far) it would be great to have an explanation as to why these compiling errors happen, and if there’s another way than just disabling the preprocessor altogether.

I’m also very confused because I’ve had typedefs everywhere in my code for such a long time and it’s only now that I switched to the OS v4.0.0 that it’s causing issues. Is it because of the PRODUCT_ID macro not being there anymore?

Thanks in advance.

1 Like

My guess would be that the preprocessor scans the code for any declared function and when it cannot find a function prototype for one it will create it at the top of the code before your type definition.

3 Likes

ScruffR is correct.

I find the behavior of the Arduino preprocessor annoying so I just give my main file a .cpp filename extension instead of .ino and add:

#include "Particle.h"

Using .cpp has the same effect as PARTICLE_NO_PREPROCESSOR except you only have one .cpp file, not both a .ino and .cpp files with the same contents, which is a pain for search in Workbench.

3 Likes