Arduino UNO - undefined pointer to class question

Hi everyone,

I was thrown off by something I saw in a program this morning, and I was hoping someone could explain why / how it works. A pointer to a class was created, but the pointer was never assigned a value of the address of an instance of a class, but calling methods from the pointer worked as expected. I would expect the pointer value to be pointing to a random value, and using it to execute methods would… not work as expected.

Here’s a code example:

#include <Arduino.h>

class PowerControlClass
{
 protected:
 public:
	void Power_Up(); 
	void Power_Down(void);
};

void PowerControlClass::Power_Up()
{
    digitalWrite(LED_BUILTIN, LOW);
}

void PowerControlClass::Power_Down(void)
{
    digitalWrite(LED_BUILTIN, HIGH);
}

PowerControlClass *pointer;
PowerControlClass instance;

void setup() {
    // put your setup code here, to run once:
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);
}

void loop() {
    pointer->Power_Up();
    delay(3000);
    pointer->Power_Down();
    delay(3000); 
}

This it is completely undefined behavior. The pointer

should be been default-initialized to 0. Any uninitialized global variable belongs to the .bss segment which the startup code (.S) clears out as pretty much the first action after boot. And calling to a nullptr is undefined. By any means this should just crash. In very special circumstances when the right functions are placed at the right places (e.g. flash mapping starts at 0x0 and there’s some code there and it hits a return instruction pretty quickly), it might not crash, but that is by absolute chance.

1 Like

OK… I thought so. It’s pretty shocking that the program I originally found it in, and the example that I made both happened to “work as expected”. Who knows what was actually happening under the hood.

Thanks for the quick clarification @maxgerhardt!

Actually it is not so shocking as it might look at a first glance: Member functions are stored only once per class not once per instance. Thus, to call them the compiler only needs to know the type of the class instance on which the member function is called.

In your example the type of the class instances is PowerContrlClass for both, instance and pointer. So the compiler will call the correct function. Additionally, it will pass the address of your class instance via the hidden this parameter to the member functions. If you call through instance you’ll get a correct value, if you call through your pointer (which is initialized to 0) you’ll get an invalid address (0). But, as long as you don’t access member variables you don’t need the address of the class instance and everything works nicely. As soon as you try to access a member variable from your member function your example will crash of course.

You can see what happens, if you print the passed in this pointer in your member functions:

#include <Arduino.h>

class PowerControlClass
{
 public:
    void Power_Up();
    void Power_Down(void);
};

void PowerControlClass::Power_Up()
{
    digitalWrite(LED_BUILTIN, LOW);
    Serial.printf("this: %p\n", this);
}

void PowerControlClass::Power_Down(void)
{
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.printf("this: %p\n", this);
}

PowerControlClass* pointer;
PowerControlClass instance;

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);  
}

void loop()
{
    Serial.println("Instance----------");
    instance.Power_Up();
    delay(500);
    instance.Power_Down();
    delay(500);

    Serial.println("Pointer----------");
    pointer->Power_Up();
    delay(500);
    pointer->Power_Down();
    delay(500);
}

I don’t have an Uno, but on a Teensy 4.1 this prints:

Instance----------
this: 0x20001a54
this: 0x20001a54
Pointer----------
this: 0x0
this: 0x0
Instance----------
this: 0x20001a54
this: 0x20001a54
... 

If you initialize pointer correctly

PowerControlClass instance;
PowerControlClass* pointer = &instance;

you’ll get:

Instance----------
this: 0x20001a54
this: 0x20001a54
Pointer----------
this: 0x20001a54
this: 0x20001a54

Nevertheless, as @maxgerhardt pointed out, calling a member through a not initialized pointer is probably undefined behaviour. At least it is ugly and smells.

1 Like

Thank you @luni64, I appreciate the additional details. Compilers are black magic as far as I am concerned. I really should learn more about how they work.

Agreed.