1

I am trying to pass a pointer to a const char * from a child to its parent, but I am clearly not understanding how to do this correctly. Here is the code which contains three classes: 1) parent 2) Alpha (child) 3_ Beta (child)

#pragma once
class Parent {
protected:
    char *childName;

public:
    Parent ( char* _childName ) {
        childName = _childName;
    }

    char *getChildName () {
        return childName;
    }
};

class Alpha: public Parent {
protected:
    const char* alphaName = "ALPHA";

public:
    Alpha (): Parent ( alphaName ) {
        Serial.print ( F ( "My name is " ) );
        Serial.println ( getChildName () );
    }
};

class Beta: public Parent {
protected:
    const char* betaName = "BETA";

public:
    Beta (): Parent ( betaName ) {
        Serial.print ( F ( "My name is " ) );
        Serial.println ( getChildName () );
    }
};

Alpha *alpha;
Beta *beta;
void setup () {
    Serial.begin ( 115200 );
    while (!Serial.availableForWrite ()) {}

    alpha = new Alpha ();
    beta = new Beta ();
}

void loop() {}

And here is the output I get: My name is �

My name is �˵

  • try to add const to all char*. on what mcu architecture do you compile and run it? – Juraj Jun 25 '19 at 9:05
  • 1
    I tried your code, after adding the missing const to have it compile, and I get the expected output: "My name is ALPHA\r\nMy name is BETA\r\n". – Edgar Bonet Jun 25 '19 at 10:07
  • A long time ago a senior programmer told me the correct way of defining literals was to do const char betaName[] = "BETA"; unfortunately I can't remember the precise details but it was to do with the way the memory was allocated. HOWEVER this was back in the 90's so hopefully the compilers are better now. – Code Gorilla Jun 25 '19 at 10:40
  • The short version is that const will entice the compiler to use program memory (which is usually more abundant) to store the value rather then data memory (which is usually less abundant). Notice all the "qualifiers" I used? This is because different compilers and platforms combinations will likely have different results. – st2000 Jun 25 '19 at 12:21
2

The problem here is that alphaName and betaName are member variables. That means they are initialized during the Alpha and Beta constructors. However, the superclass constructor of Parent is always called first, before calling the child class constructors. Result: Parent::Parent(char *) is called with an uninitialized pointer.

Here is your code on the Compiler Explorer: https://godbolt.org/z/otAuQU As you can see, Alpha::Alpha() compiles to the following:

call Parent::Parent(char*)
ldd r24,Y+1
ldd r25,Y+2
ldi r18,lo8(.LC0)
ldi r19,hi8(.LC0)

.LC0 is the string "ALPHA".

The solution here is to make the names static. That way, they are initialized before the parent constructor is called.

class Parent {
  protected:
    const char *const childName;

  public:
    Parent(const char* childName) : childName(childName) {}

    const char *getChildName () const {
        return childName;
    }
};

class Alpha : public Parent {
  protected:
    constexpr static const char *alphaName = "ALPHA";

  public:
    Alpha() : Parent(alphaName) {
        Serial.print(F("My name is "));
        Serial.println(getChildName());
    }
};

This compiles to:

ldi r22,lo8(.LC0)
ldi r23,hi8(.LC0)
call Parent::Parent(char const*)

As others have mentioned, pointers to string literals should always be const. The only reason this doesn't give an error is because the Arduino folks decided that it was a good idea to compile everything with -fpermissive ...
Mutating a string literal is undefined behavior, so an error.

  • 1
    And the award goes to tttapa who has now fried a few of my remaining brain cells with his precise and correctly working code sample. Thanks, tttapa! And now I have a lot of code that needs fixing. – Bob Jones Jun 25 '19 at 23:07
  • @BobJones, I think using const char* should be enough and Edgar Bonet reported it working in a comment. But we don't know the platform you are using – Juraj Jun 26 '19 at 9:25
  • @Juraj, I think that's still undefined behavior. The reason that it appears to be working is that the compiler optimizes it because it knows that it will be constant. However, there is no guarantee that the pointer should be initialized before calling the parent constructor, it's just a happy coincidence. – tttapa Jun 26 '19 at 9:34
  • C++ reference "Before the compound statement that forms the function body of the constructor begins executing, initialization of all direct bases, virtual bases, and non-static data members is finished." en.cppreference.com/w/cpp/language/initializer_list – Juraj Jun 26 '19 at 9:43
  • @Juraj, I'm not talking about the constructor body, I'm talking about the Parent constructor. First the Parent::Parent(const char *) constructor gets called, the Parent member variables are initialized (childName), then data members of Alpha are initialized (alphaName), and finally, the body of Alpha::Alpha() is executed. I just tested the original code with added const on an UNO with version 1.6.23 of the AVR Core. It doesn't work. When the names are static, it does work. – tttapa Jun 26 '19 at 9:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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