0

I was wondering whether assert( this != nullptr ); was a good idea in member functions and someone pointed out that it wouldn’t work if the value of this had been added an offset. In that case, instead of being 0, it would be something like 40, making the assert useless.

When does this happen though?

qdii
  • 12,505
  • 10
  • 59
  • 116
  • Isn't it undefined behaviour to call a function on a null-pointer? – jogojapan Jul 21 '13 at 13:22
  • If `this` was derived by indexing an array of objects, where the array basing pointer was null. – Hot Licks Jul 21 '13 at 13:23
  • 1
    @jogojapan well `assert()` is actually used to debug bugs :) – qdii Jul 21 '13 at 13:23
  • I don't remember if it's undefined behaviour. But if it is, `assert` doesn't help. – jogojapan Jul 21 '13 at 13:25
  • It is UB, but assert *might* help in non-optimized debug build. – zch Jul 21 '13 at 13:27
  • @jogojapan I'd say yes, because the member-access for member functions is resolved from `a->b` to `(*a).b`. – dyp Jul 21 '13 at 13:27
  • @jogojapan I think it is UB by the standard but I think such assert would still help. My rationale is: after `Foo::bar()` is compiled, it looks just like a C function `Foo_bar( Foo* this );`, so I am just checking a C function parameter. – qdii Jul 21 '13 at 13:27
  • 1
    @qdii I understand what you are saying. But _undefined behaviour_ means it is not defined what happens when you have such a call in your code. In particular, there is no guarantee that the point of the `assert` is ever reached. Related: http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-beha – jogojapan Jul 21 '13 at 13:29
  • Note this is probably ineffective for virtual functions. – dyp Jul 21 '13 at 13:35
  • If this is null then its instance was destroyed, now where is actually this stored? Maybe it is compiler specific? If memory for instance was released, then some other thread might occupy its address range, making this non null. This way you cannot rely on such assertion. – marcinj Jul 21 '13 at 13:35
  • 3
    Optimizing compilers have a tendency to optimize on the assumption that undefined behavior does not occur. Dereferencing a null pointer is clearly undefined behavior, so `assert(this != NULL);` may well be optimized out altogether. – Casey Jul 21 '13 at 13:36

6 Answers6

8

Multiple inheritance can cause an offset, skipping the extra v-table pointers in the object. The generic name is "this pointer adjustor thunking".

But you are helping too much. Null references are very common bugs, the operating system already has an assert built-in for you. Your program will stop with a segfault or access violation. The diagnostic you'll get from the debugger is always good enough to tell you that the object pointer is null, you'll see a very low address. Not just null, it works for MI cases as well.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • +1 good point, Thanks for the "this pointer adjustor thunking" link. – Anand Rathi Jul 21 '13 at 13:50
  • I find this answer somewhat misleading. C++ isn't built on the assumption that it necessarily runs on a system that supports memory protection (or segmentation). Also, even if it does, a segmentation fault does not usually provide very good information. For the stack trace you need a core dump, which you may not get, depending on the circumstances of the crash. And for the stack trace to be useful, you need debug symbols. Finally, calling a member function on a null pointer is undefined behaviour. UB is not the same as a segmentation fault, even on a system with memory segmentation. – jogojapan Jul 22 '13 at 00:50
4

this adjustment can happen only in classes that use multiple-inheritance. Here's a program that illustrates this:

#include <iostream>

using namespace std;

struct A {
  int n;
  void af() { cout << "this=" << this << endl; }
};

struct B {
  int m;
  void bf() { cout << "this=" << this << endl; }
};

struct C : A,B {
};

int main(int argc, char** argv) {

  C* c = NULL;
  c->af();
  c->bf();

  return 0;
}

When I run this program I get this output:

this=0
this=0x4

That is: your assert this != nullptr will not catch the invocation of c->bf() where c is nullptr because the this of the B sub-object inside the C object is shifted by four bytes (due to the A sub-object).

Let's try to illustrate the layout of a C object:

0:  | n |
4:  | m |

the numbers on the left-hand-side are offsets from the object's beginning. So, at offset 0 we have the A sub-object (with its data member n). at offset 4 we have the B sub-objects (with its data member m).

The this of the entire object, as well as the this of the A sub-object both point at offset 0. However, when we want to refer to the B sub-object (when invoking a method defined by B) the this value need to be adjusted such that it points at the beginning of the B sub-object. Hence the +4.

Itay Maman
  • 30,277
  • 10
  • 88
  • 118
1

Note this is UB anyway.

Multiple inheritance can introduce an offset, depending on the implementation:

#include <iostream>

struct wup
{
    int i;
    void foo()
    {
        std::cout << (void*)this << std::endl;
    }
};

struct dup
{
    int j;
    void bar()
    {
        std::cout << (void*)this << std::endl;
    }
};

struct s : wup, dup
{
    void foobar()
    {
        foo();
        bar();
    }
};

int main()
{
    s* p = nullptr;
    p->foobar();
}

Output on some version of clang++:

0
0x4

Live example.


Also note, as I pointed out in the comments to the OP, that this assert might not work for virtual function calls, as the vtable isn't initialized (if the compiler does a dynamic dispatch, i.e. doesn't optimize if it know the dynamic type of *p).

dyp
  • 38,334
  • 13
  • 112
  • 177
0

Here is a situation where it might happen:

struct A {
    void f()
    {
       // this assert will probably not fail
       assert(this!=nullptr);
    }
};

struct B {
    A a1;
    A a2;
};

static void g(B *bp)
{
    bp->a2.f(); // undefined behavior at this point, but many compilers will
                // treat bp as a pointer to address zero and add sizeof(A) to
                // the address and pass it as the this pointer to A::f().

}

int main(int,char**)
{
    g(nullptr); // oops passed null!
}

This is undefined behavior for C++ in general, but with some compilers, it might have the consistent behavior of the this pointer having some small non-zero address inside A::f().

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Normally, I'd put the `assert` inside `g`, so the `assert` in `f` is just another layer of debugging? – dyp Jul 21 '13 at 13:29
0

Compilers typically implement multiple inheritance by storing the base objects sequentially in memory. If you had, e.g.:

struct bar {
  int x;
  int something();
};

struct baz {
  int y;
  int some_other_thing();
};

struct foo : public bar, public baz {};

The compiler will allocate foo and bar at the same address, and baz will be offset by sizeof(bar). So, under some implementation, it's possible that nullptr -> some_other_thing() results in a non-null this.

This example at Coliru demonstrates (assuming the result you get from the undefined behavior is the same one I did) the situation, and shows an assert(this != nullptr) failing to detect the case. (Credit to @DyP who I basically stole the example code from).

Casey
  • 41,449
  • 7
  • 95
  • 125
-3

I think its not that bad a idea to put assert, for example atleast it can catch see below example

class Test{
public:
void DoSomething() {
  std::cout << "Hello";
}
};

int main(int argc , char argv[]) {
Test* nullptr = 0;
 nullptr->DoSomething();
}

The above example will run without error, If more complex becomes difficult to debug if that assert is absent.

I am trying to make a point that null this pointer can go unnoticed, and in complex situation becomes difficult to debug , I have faced this situation.

Anand Rathi
  • 790
  • 4
  • 11
  • 1
    I don't see how you can assert that a program that unconditionally invokes undefined behavior "will run without error." – Casey Jul 21 '13 at 13:37
  • I think multiple inheritance will introduce an offset for functions inside (some of the) base classes. – dyp Jul 21 '13 at 13:37
  • 4
    The question is about when it will have an offset, so this doesn't answer it. – interjay Jul 21 '13 at 13:38
  • try @Casey before passing judgment – Anand Rathi Jul 21 '13 at 13:38
  • @interjayi I was trying to answer this "I was wondering whether assert( this != nullptr ); was a good idea in member functions" – Anand Rathi Jul 21 '13 at 13:40
  • @marcin_j Thanks , I am trying to make a point that null this pointer can go unnoticed , and in complex situation becomes difficult to debug , I have faced this situation. – Anand Rathi Jul 21 '13 at 13:41