Double pointer problem

Started by
40 comments, last by Melekor 15 years, 4 months ago
Hi, I spent the last 3 days trying to figure out what the problem is in the following code. I actually know what the problem is and I even know how to fix it, but whats bothering me is that I completely don't understand why it's happening. Here's the code:

class Base
{
public:
	
};

class D : public Base
{
public:
	virtual void hi() = 0;
};

class D2: public D
{
public:
	void hi() {cout << "From d2" << endl;}
};

void func(Base** b)
{
	*b = new D2;
}

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


	D* d =0;
        func((Base**)&d);	
	d->hi();

       return 0;
}





What happens is that the address of the "hi" function in the virtual table of d is 0x00000000 so Access violation pops up when I call "hi" through d. But why is it so? Someone told me that the pointer to the base class is not the same as the pointer to the derived class, but I don't understand what that means. I checked both the derived pointer and the base pointer and they are the same. Thanks [Edited by - VanillaSnake21 on November 28, 2008 1:03:17 AM]

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Advertisement
Without the cast, this is the error I get:
Quote:
error C2664: 'func' : cannot convert parameter 1 from 'D **' to 'B **'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

The cast means "shut your face compiler, I know more than you do". The problem here is that you do not know more than the compiler. A start might be here.

Before using a cast to quiet the compiler - be 110% sure you know what you are doing. And don't use a C style cast, use a C++ cast.

Quote:
I checked both the derived pointer and the base pointer and they are the same.

Don't think about the pointer values. It is the types that are the issue. Allowing usch a cast would break the type system.
Sorry, yea the B should be replaced with Base.
Quote:Before using a cast to quiet the compiler - be 110% sure you know what you are doing. And don't use a C style cast, use a C++ cast.


But I actually am sure of what I'm doing, the page you have directed me to, I've read many times before, and I understand why the types are incompatible, but I know that they are compatible in this case because D is in fact a derived class of B, so it's not like I'm trying to assign D to it's sibling (which I know is possible with double pointers). Even if I use the C++ casts the problem doesn't go away. What I'm trying to understand is why the v-table pointer to "hi" function is 0x0000000.

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Quote:
I actually am sure of what I'm doing..

I'm afraid I'm forced to disagree [smile]

Perhaps this more verbose version will show you:
class B{public:	};class D : public B{public:	virtual void hi() = 0;};class D2: public D{public:	void hi() {std::cout << "hi  : Hello, World\n";}};void func(B** b){	D *d = new D2;	*b = d;	std::cout << "func: d is " << d << '\n';	std::cout << "func: *b is " << *b << '\n';	d->hi();	std::cout << "func: ending\n";}int main(int argc, char* argv[]) {	D* d =0;	func((B**)&d);		std::cout << "main: d is " << d << '\n';	d->hi();}


Output (before it crashes):
Quote:
func: d is 00345FD8
func: *b is 00345FDC
hi : Hello, World
func: ending
main: d is 00345FDC

C++ is free to have multiple addresses for the same object. Correct casting is required so that C++ knows how to adjust the pointers. This is most often seen in multiple inheritance. The reason for it here (under this specific implementation) is probably because "B" lacks any virtual functions, so it won't have a vtable (again, even the existence of vtables are implementation defined).

Indeed, adding an empty virtual destructor to B makes the program run without crashing on my machine. However, the behaviour is still undefined.
Here's what it's happening:

*b = new D2 stores a valid Base* pointer in d (pointing to the Base sub-object of a D2 object). Then, d->hi() dereferences d expecting to find a D object or sub-object at that location. However, what it finds is a Base, and it just so happens that a Base has a zero where the vtable of a D should be, so the call dies.

In short, when you wrote (Base**)&d, you told the compiler "You will let me store pointers of type Base* in d". Then, you did exactly that. And then, you tried to access the contents of d as if they were a pointer of type D*, which it wasn't.
Original post by rip-off

Output (before it crashes):
Quote:
func: d is 00345FD8
func: *b is 00345FDC


I probably didn't test the pointers right so they ended up being equal, but here *b is 4 bytes off, why is that? If you said *b = d; how can they not be equal?



Quote:
C++ is free to have multiple addresses for the same object. Correct casting is required so that C++ knows how to adjust the pointers.


Someone told me almost the same thing. What exactly does it mean "adjust pointers", also how can one object have multiple addresses? Can you please explain the exact reason why the v-table fails to get intialised properly. Thanks.

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

The vtable is getting initialized properly. The problem is you told the compiler that the pointer was a pointer to Base when it wasn't.
Quote:Original post by ToohrVyk
Here's what it's happening:

*b = new D2 stores a valid Base* pointer in d (pointing to the Base sub-object of a D2 object). Then, d->hi() dereferences d expecting to find a D object or sub-object at that location. However, what it finds is a Base, and it just so happens that a Base has a zero where the vtable of a D should be, so the call dies.

In short, when you wrote (Base**)&d, you told the compiler "You will let me store pointers of type Base* in d". Then, you did exactly that. And then, you tried to access the contents of d as if they were a pointer of type D*, which it wasn't.



I don't understand,
b* = new D2 stores a valid Base* pointer in d

Why does it store a Base*, doesn't new D2 return a pointer to a D2 object?

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Quote:Original post by SiCrane
The vtable is getting initialized properly. The problem is you told the compiler that the pointer was a pointer to Base when it wasn't.


But how does that make a difference, why is a pointer to base different from a pointer to derived? Isn't the v-table the first thing in the memory layout of a derived class, followed by the base object, followed by the derived object?

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

Memory layout for non POD types is implementation defined AFAIK.

Quote:
Isn't the v-table the first thing in the memory layout of a derived class, followed by the base object, followed by the derived object?

Lets assume this is the case.

Let us assume also that "B" has a member.

Tell me how I can pass a D pointer to a function (that knows nothing of existance of the D class, only B) that manipulates this member.

In code:
struct B{   int member;};void frobnicate(B *b){    b->member = 42;}struct D : B{   virtual void problem(){}};int main(){    D d;    frobnicate(&d);}

This topic is closed to new replies.

Advertisement