Pointer to AudioPocessor is always valid!?

I’m having some really strange behvaiour, where an unitialized pointer to my audio processor is valid, even if I explicitely set it to nullptr.

in Knob.h:

OdinAudioProcessor* m_processor = nullptr; //the ptr is never set anywhere

In Knob.cpp

m_processor = nullptr;//just make it nullptr again to flex on Bjarne Stroustrup
m_processor->startMidiLearn(this); //works just fine!??

of course I’d be happy and move on, but this is future segfault territory.
Is there some C++ feature unknown to me, which makes a pointer of a specific class always the same static object? This is just a shot in the blue…

Ok after further research I found:

if the function being called is “static” (not defined as static but would qualify) it works… if not so, it segfaults:

//this works fine
startMidiLearn(Knob* p_knob){
  DBG("MIDI learn");
}
//this does not
startMidiLearn(Knob* p_knob){
  DBG("MIDI learn");
  m_midi_learn_knob = p_knob;
}

If your function doesn’t access any class data members, it may appear to work fine. For example, the following program appears to ‘work’:

#include <iostream>

struct MyType {
    void doSomethingWithoutAccessingDataMember() {
        std::cout << "got here\n";
    }
};

int main() {
    MyType* ptr = nullptr;
    ptr->doSomethingWithoutAccessingDataMember();
    return 0;
}

Is this a good idea? Absolutely not. You should always explicitly check before dereferencing a pointer that may be null. It’s just one of those things you have to remember, like avoiding dividing by values that may be zero.

How can you find these kinds of issues? Undefined Behaviour Sanitizer is really good at finding issues like this. If I build the above snippet with clang++ scratch.cpp -std=c++11 -fsanitize=undefined and then run it, I get the following helpful output:

scratch.cpp:11:10: runtime error: member call on null pointer of type 'MyType'
scratch.cpp:4:10: runtime error: member call on null pointer of type 'MyType *'

If I run the program in lldb, it even pauses on the problematic lines.

2 Likes

Cool, thanks for your answer!

That is against everything I thought i knew about pointers. What’s going on under the hood? Are “pseudo-static” functions in some way not called on the actual class?

You can think of member functions as if they had an additional first hidden argument.

So

class Foo
{
    int bar (float someArg);
}

gets translated to something like this under the hood

int FooMemberFunction_bar (Foo* this, float someArg); 

This means that this piece of code

Foo* f;
f->bar (2.5f);

gets translated to a call like this

Foo* f;
FooMemberFunction_bar (f, 2.5f);

A non-virtual member function does not need any piece of data of the class instance it is called on to be just called, it can be called like any other normal function in the first place, however it will obviously fail at the point where the this pointer is invalid and gets dereferenced.

A static member function is simply lacking the this argument. That’s why you can convert static member functions to regular function pointers by the way

1 Like