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 �˵
1 Answer 1
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.
-
1And 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– Bob Jones2019年06月25日 23:07:47 +00:00Commented Jun 25, 2019 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 using2019年06月26日 09:25:58 +00:00Commented Jun 26, 2019 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– tttapa2019年06月26日 09:34:27 +00:00Commented Jun 26, 2019 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_list2019年06月26日 09:43:58 +00:00Commented Jun 26, 2019 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, theParent
member variables are initialized (childName
), then data members ofAlpha
are initialized (alphaName
), and finally, the body ofAlpha::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– tttapa2019年06月26日 09:48:19 +00:00Commented Jun 26, 2019 at 9:48
const
to have it compile, and I get the expected output: "My name is ALPHA\r\nMy name is BETA\r\n".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.