Reputation: 12983
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?
Upvotes: 0
Views: 262
Reputation: 30733
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.
Upvotes: 4
Reputation: 42594
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).
Upvotes: 0
Reputation: 39151
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
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
).
Upvotes: 1
Reputation: 786
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.
Upvotes: -3
Reputation: 942408
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.
Upvotes: 8
Reputation: 64308
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()
.
Upvotes: 0