I am having trouble getting my head around pointers in C++, specifically how they behave when passed around or used within objects. As far as my understanding goes as long as pointers, or more specifically values/objects pointed to - do not go out of scope, I should be able to use that pointer.
I have been playing around with some test code on my Arduino Uno and I am bumping into behaviour I cannot explain.
I have read rather a lot on the topic of pointers in C++ and thought I had a good grasp of it. However, practice doesn't seem to match the theory in my head. I don't know if this is a lack of understanding or if the Arduino platform has idiosyncrasies that I am not aware of.
In my test code I have created two classes, Alpha
and Beta
. Alpha simply holds a C-type string in a member variable called alpha
. Beta
is designed to hold two Alpha
instances in an array*.
*While this is "toy" code, the general structure mirrors my actual project. I have used this toy code as it more succinctly describes the set up and the issue I am facing.
pointers.h
#import "Arduino.h"
class Alpha {
private:
char alpha[20];
public:
Alpha();
~Alpha();
char* getAlpha(char* s, int size=20);
void setAlpha(char* value, int size=20);
void debug();
};
class Beta {
private:
Alpha** alphas;
public:
Beta();
~Beta();
Alpha* getAlpha(int index);
void setAlpha(Alpha* alpha, int index);
void doStuffWithAlpha(int index);
};
pointers.cpp
#import "pointers.h"
Alpha::Alpha() {
setAlpha("Initial");
}
Alpha::~Alpha() {}
char* Alpha::getAlpha(char* s, int size/*=20*/) {
*s = *alpha;
return s; //return not strictly needed here since s is passed in
}
void Alpha::setAlpha(char* value, int size/*=20*/) {
strncpy(alpha, value, size-1);
alpha[size-1] = '\0';
}
void Alpha::debug() {
Serial.println(alpha);
}
Beta::Beta() {
alphas = new Alpha*[2];
}
Beta::~Beta() {}
Alpha* Beta::getAlpha(int index) {
return alphas[index];
}
void Beta::setAlpha(Alpha* alpha, int index) {
alphas[index] = alpha;
}
void Beta::doStuffWithAlpha(int index) {
Alpha* alpha = getAlpha(index);
/* Alpha* alpha = alphas[index]; */ //This doesn't work either - same outcome.
alpha->debug();
}
sketch.ino
#import "pointers.h"
Alpha a1;
Alpha a2;
Beta b1;
void setup() {
Serial.begin(9600);
Serial.println("SETUP()");
Serial.println();
a1 = Alpha();
a1.setAlpha("Hello");
Serial.println("----- a1.debug -----");
Serial.println("Expected: Hello");
Serial.print("Actual: "); a1.debug();
Serial.println();
a2 = Alpha();
a2.setAlpha("Goodbye");
Serial.println("----- a2.debug -----");
Serial.println("Expected: Goodbye");
Serial.print("Actual: "); a2.debug();
Serial.println();
Beta b1 = Beta();
b1.setAlpha(&a1, 0);
b1.setAlpha(&a2, 1);
Serial.println("----- b1.doStuffWithAlpha -----");
Serial.println("Expected: Hello");
Serial.print("Actual: "); b1.doStuffWithAlpha(0);
Serial.println("Expected: Goodbye");
Serial.print("Actual: "); b1.doStuffWithAlpha(1);
Serial.println();
}
void loop() {
Serial.println("LOOP()");
Serial.println();
Serial.println("----- a1.debug -----");
Serial.println("Expected: Hello");
Serial.print("Actual: "); a1.debug();
Serial.println();
Serial.println("----- b1.doStuffWithAlpha(0) ------");
Serial.println("Expected: Hello");
Serial.print("Actual: "); b1.doStuffWithAlpha(0);
Serial.println();
Serial.println("----- a2.debug -----");
Serial.println("Expected: Goodbye");
Serial.print("Actual: "); a2.debug();
Serial.println();
Serial.println("----- b1.doStuffWithAlpha(1) ------");
Serial.println("Expected: Goodbye");
Serial.print("Actual: "); b1.doStuffWithAlpha(1);
Serial.println();
delay(10000);
}
This sketch is quite straight-forward. It creates 2 Alpha
objects and a Beta
object and passes the two Alphas into Beta. The myriad Serial.print()
statements produces the output below.
output
SETUP()
----- a1.debug -----
Expected: Hello
Actual: Hello
----- a2.debug -----
Expected: Goodbye
Actual: Goodbye
----- b1.doStuffWithAlpha -----
Expected: Hello
Actual: Hello
Expected: Goodbye
Actual: Goodbye
//everything is fine up to this point...
LOOP()
----- a1.debug -----
Expected: Hello
Actual: Hello
----- b1.doStuffWithAlpha(0) ------
Expected: Hello
Actual: CÃoçëyc=Lä^ÜŽYw¸Nv†Ý¿ //uh oh...
----- a2.debug -----
Expected: Goodbye
Actual: Goodbye
----- b1.doStuffWithAlpha(1) ------
Expected: Goodbye
Actual: ÿß/ï //uh oh again...
Everything works as expected when running a1
or a2.debug()
or b1.doStuffWithAlpha()
in the setup()
function.
The result of a1.debug()
and a2.debug()
works as expected in the loop()
function.
The issue, as you can see, is when running b1.doStuffWithAlpha(0)
or b1.doStuffWithAlpha(1)
. The output is usually junk, which suggests to me the pointer has been mangled and points in the wrong place.
I don't understand what is happening between the transition from setup()
to loop()
. At first I thought the issue was the "Hello" and "Goodbye" strings going out of scope, but Alpha::setAlpha()
makes a copy of the C-string. Also, accessing Alpha::debug()
in loop()
works, as shown above.
I have tried variations on the above code, such as using a struct or an int for alpha
instead of a C-string and the results are the same: Mangled values.
Have I done something obviously silly that my scant C++ experience hasn't picked up on, or is there some fundamental Arduino shenanigans at play?