0

Below is a minimal example of problem that occurred in a huge legacy project I am currently working on.

I have a main function that should be creating a new object ob a global object pointer B* B. Because of a configuration error this was not done and the non-static method b->do_something() was called. My expectation would be that this leads to a segmentation fault when doing so.

The code does create a segmentation fault but only when comparing a == nullptr. This was extremely confusing to me because I was looking for problems with the initialization of a. I was not aware that calling a non-static method of an object is possible even when this object was never initialized.

My question is how to prevent this confusion in the future? So how do I crash the execution when calling a non-static method of a NULL object?

Possible solutions that do not satisfy me yet:

  • Wrapping every call to b->do_something() with if(b != nullptr)
    • Very verbose
  • Changing do_something() to if (&a == nullptr)
    • Works but flagged by clang-tidy (CI check fails)
    • According to this a "A reference can not be NULL"
  • Extending do_something() with if (this == nullptr || a == nullptr)
    • Works but flagged by clang-tidy (CI check fails)
#include <stdio.h>
#include <stdexcept>

class A {};

class B {
    A *a = nullptr;
public:
    B(){
        // Sometimes doing this
        a = new A();
    }
    void do_something() {
        if(a == nullptr){ // SegFault appears here because B was never created and accessing this->a evaluates to nullptr->a
            throw std::runtime_error("Error");
        } else {
            printf("Doing Something");
        }
    } 
};


// Global variable
B *b;

int main()
{
    // Long code that should have called:
    // b = new B();
    b->do_something();

    return 0;
}
Kaio1012
  • 25
  • 3
  • It's not so much that `B` was not constructed as much as `b` is not assigned to anything. As a global variable, it should initially be null. You could try `if (b) b->do_something();`. – Fred Larson Apr 27 '21 at 12:57
  • Ideally (for safety), each pointer access should be checked. (once checked, you might pass by reference instead of pointer to avoid extra check). – Jarod42 Apr 27 '21 at 13:00
  • Related : [Why doesn't the OS crash if I dereference a null pointer?](https://stackoverflow.com/questions/9394582/why-doesnt-the-os-crash-if-i-dereference-a-null-pointer) – François Andrieux Apr 27 '21 at 13:25

1 Answers1

2

You cannot prevent a user from all possible mistakes and most of the time it is futile to even try (this isnt Java ;). Thou shall not call a member on a non-initialized pointer (actually in your code it is initialized because it is global, but with nullptr). Thats not specific to your class, but something one has to consider always when using pointers.

However, there are ways to mitigate the problem. You could make B::do_something private and be-friend a free function:

void do_something(B* b) {
    if (b) b->do_something();
}

Its not very clear from your minimal example, but if do_something can be implemented as non-member, then you do not need the member function in the first place.

My question is how to prevent this confusion in the future? So how do I crash the execution when calling a non-static method of a NULL object?

There is no portable way to do that. Calling a member function on a nullptr is undefined behavior. Anything can happen, including it appears to work. If you remove the check for a being nullptr then do_something is actually not using this and most probably will appear to work with most compilers.

Checking this != nullptr inside the method is also no help, because once the call is made, you are already in undefined behavior.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you for your fast answer! You are right `do_something` could be implemented as a non-member function. I just liked the "Java-Style" object approach to it. But if it can lead to such debugging issues I might need to change my mind on it. – Kaio1012 Apr 27 '21 at 13:08
  • @Kaio1012 as I wrote, the problem is not with your class, the problem is with someone writing `b->do_something()` without being certain that `b` points to an object, and thats not something you are in control of. – 463035818_is_not_an_ai Apr 27 '21 at 13:09
  • You are completely right, I was just not expecting that. Maybe one additional question what difference does it then make adding the static keyword from a compiled program view? Is there more to it then a Compiler error when using `this` in that function? – Kaio1012 Apr 27 '21 at 13:19
  • @Kaio1012 be careful with [`static`](https://en.cppreference.com/w/cpp/keyword/static), it has different meaning depending on context. A static method belongs to the class while a non static method belongs to instances, ie there is no `this` that you could use in a static method – 463035818_is_not_an_ai Apr 27 '21 at 13:22
  • Okay, got it! Thank you very much for your help :) – Kaio1012 Apr 27 '21 at 13:25