3

I want to have a macro defined in a class. I wish to define it first in my main, but use an #ifndef to create it if the user chooses not to. For some reason it would seem like the class is being linked before the main, so my #define in main is not being used. TIA

My main ino

#define TEST_MACRO 4
#include "test.h"

test testclass;
void setup() {
  Serial.begin(9600);
  
  testclass.showValue();
}

void loop() {
  
}

test.h

#ifndef TEST_MACRO
  #define TEST_MACRO 5
#endif
#include "Arduino.h"

class test
{
  public:
    test();
    void showValue();
  private:
    uint16_t testval = TEST_MACRO;
};

test.cpp

#include "test.h"

test::test()
{
  ;
}

void test::showValue()
{
  Serial.println(testval);
}

Expected Result "4" Result "5"

However, if I do this with a test.h file include and no class, it all works as expected.

6
  • 1
    test.cpp doesn't know anything about the macros you've defined in main.ino Commented Feb 24 at 5:21
  • I'm not asking it to. I'm asking test.h to. If I don't put in "class test" it works fine. main.ino knows all about the macros defined in test.h I thought the compiler didn't care about files and scope, and just processed #define as it came upon them? ie test.h as follows ``` #ifndef TEST_MACRO #define TEST_MACRO 5 #endif #include "Arduino.h" void showValue() { Serial.println(TEST_MACRO); } ``` Works just fine. Commented Feb 24 at 6:00
  • 2
    The implementation of the initialisation of testval is in test::test at which point the macro is defined to 5 so that's the value you get. You've violated the ODR though so it's undefined behaviour Commented Feb 24 at 7:26
  • How does it violate the ODR? That's thrown me completely. Does not the #ifndef cover any ODR violations? I get that the macro is 5, but what I don't get is why it wasn't predefined in Main to be 4. as it was defined before the class was included. I guess this is an integral misunderstanding of mine I'm going tohave to research. If you knew of a resource I can bury myself in reading, I'd appreciate it. Commented Feb 25 at 10:29
  • 1
    uint16_t testval = 4 is a definition, your two cpp files contain different versions of this definition Commented Feb 25 at 11:00

1 Answer 1

5

The problem here is that your use of macros means that you have two different versions of the test class. Macros are resolved early on in the compilation process, so this means that in main you have this class

class test
{
    ...
    uint16_t testval = 4;
    ...
};

but in test.cpp you have this class

class test
{
    ...
    uint16_t testval = 5;
    ...
};

The problem here is that C++ does not allow you to have different definitions of a class. Classes included in header files get defined multiple times, but all the definitions must be identical. This is part of what is called the one definition rule (ODR).

Programs that violate ODR have undefined behaviour and furthermore compilers are not required to diagnose the violation, so you cannot expect to see any compiler or run time error message. Instead you just get unpredictable behaviour from your code.

It's not clear to me what real world problem you are trying to solve using this approach, but it cannot work due to the ODR violation. Perhaps you should explain what you are actually trying to achieve, and ask for suggestions on how to do that. Maybe a template class would fulfil your needs?

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, but I don't understand. I only have one instance of the class. I'm trying to have a default value for a colour, and to be able to set in main an override to that default colour. I'll do it with a setter, as I know that would work, I just thought that the compiler would resolve the #defines in main before the #defines in the class. After all, main can see the Class #defines, I thought if I defined it before including the class that it would be already defined. I also don't follow how I violate the ODR when I use #ifndef instructions
@Kudapucat This is not about instances of classes, it's about definitions. When you write class test { .... }; you are defining a class. You have two definitions of the test class because the header file is included in two places. Two definitions of a class is not a problem because normally both definitions are identical. But your definitions are different (because of the macros), that is the problem.
@Kudapucat Using a setter sounds like the correct solution to the problem.
John, my jaw just dropped. Thank you for taking the time to explain what should be obvious. I in fact have a ifndef TEST_H clause that I didn't post here, but that makes so much sense. If it's being called twice, my intended override is being ignored because of the #ifndef clause, and not putting it there would be worse. Thank you again.
@Kudapucat #ifndef clauses are often misunderstood. #ifndef stops a header file being included twice while compiling the same cpp file. There is nothing that can stop a header file being included twice while compiling two different cpp files. This is because C++ uses separate compilation, which means that what happens in when compiling one cpp file is not affected at all by what happens when compiling a different cpp file.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.