2

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?

4

Problem is local shadow variable b1 in setup() function declared as Beta b1 = Beta();. In loop() is considered uninitialized global variable b1.

  • 1
    I agree that is probably the problem. Also in your destructor for Beta you should delete the pointers you allocate in the constructor. I'm not wildly keen on doing new in a constructor at static scope either because of the static initialization order fiasco. – Nick Gammon Sep 10 '15 at 21:06
  • @TMa I can't believe I missed that! I must have been looking at my own code for too long! @NickGammon I agree that I should have a destructor, it was left out for just this example. Regarding the use of new in a constructor, how else would I do it? Reading the link you gave, the initialisation fiasco only affects classes/types declared in different compilation units. My code has both classes defined in the same file. – Etzeitet Sep 17 '15 at 10:50
  • If the project ever gets large enough that you have more than one compilation unit, it may affect you. What you can do is have a begin method which is called from setup (eg. like Serial). Alternatively (or as well) you could set the pointers to NULL in the constructor (which is safe) and then allocate them with new when they are needed. – Nick Gammon Sep 17 '15 at 20:38

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.