1

FileSystem() always returns NULL yet I can still call the functions of EngineFile through g_System->FileSystem()->;

I've tested this on VS2017 and one online compiler.

Obviously my intention is not to actually leave FileSystem() returning NULL because it was intended that Systems.ef get set to the EngineFile pointer. In a project I'm working on I accidentally forgot to ever set the pointer and when stepping through some other code I realized it was just returning NULL and it was still working.

I have condensed it down to a single file so all the code is right there.

Why does this work and how?

#include <iostream>

using namespace std;

class EngineFile
{
    public:
        EngineFile();
        ~EngineFile();

        const char* GetFileData(const char* filename);

        void ModifyBool(void);
    private:
        bool testmodify = false;
};

EngineFile::EngineFile()
{
}

EngineFile::~EngineFile()
{
}

void EngineFile::ModifyBool(void)
{
    testmodify = true;
}

const char* EngineFile::GetFileData(const char* filename)
{
    // open file filename
    // stub function basically
    return "This is the file data\n";
}

class Systems
{
    public:
        Systems();
        ~Systems();

        EngineFile* const FileSystem(void);

    private:
        EngineFile* ef = NULL;
};

Systems::Systems()
{
}

Systems::~Systems()
{
}

static Systems* g_System;

EngineFile* const Systems::FileSystem(void)
{
    return ef;
}

int main()
{
    EngineFile* ef = new EngineFile();
    g_System = new Systems();

    // calling the function directly works as we would think
    cout<<ef->GetFileData("test.txt");

    // calling it through indirection with FileSystem returning NULL works
    // why does this work? I feel like it should not work
    cout << g_System->FileSystem()->GetFileData("test.txt");

    // this will return null to show FileSystem returns null
    EngineFile* efnulltest = g_System->FileSystem();

    if (efnulltest == NULL)
        cout << "efnulltest is NULL\n";

    cout << "some extra text to show previous statement is still working\n";

    // segfault as expected
    g_System->FileSystem()->ModifyBool();

    return 0;
}
  • 1
    *calling it through indirection with FileSystem returning NULL works* -- Undefined behavior. No different than accessing an array element out-of-bounds. – PaulMcKenzie Apr 07 '20 at 23:24

1 Answers1

0

It works because GetFileData does not reference any class data. The function is being called, and a (hidden) null ptr is passed at the 'this' parameter. Since 'this' is not used by the function, it works just as if it was a static function.

Mark Taylor
  • 1,843
  • 14
  • 17
  • OK, so it is undefined behavior. Might not work on any other compiler, might not work for you on the next run. But because the function doesn't access any member data, the undefined behavior probably appears to work correctly for most reasonable design decisions for how the compiler handles vtables to call functions. – Mark Taylor Apr 07 '20 at 23:27
  • Okay thank you. That's what I was thinking might be happening but I wasn't completely sure if that's what it really was or not – Alora Boone Apr 07 '20 at 23:33
  • It's only undefined if I leave it returning null correct? As long as I fix that in my project, which I have, is it working in a defined way? I'm a little worried I might be doing something I shouldn't and don't realize it. – Alora Boone Apr 07 '20 at 23:36
  • Yes, if the program is fixed so that FileSystem() doesn't return a null, you no longer have undefined behavior. But this does illustrate the dangers of functions returning (possibly invalid) pointers. Look for opportunities to design the classes so the constructor of Systems fully initializes all the variables, or that a call to FileSystem() checks for the nullptr and does the necessary initialization. Advanced topics: shared_ptr or reference instead of a raw pointer. Do the hard stuff at the lowest level possible, so the higher levels don't have to worry about the details like nullptr. – Mark Taylor Apr 08 '20 at 02:57