Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++: General question pointers

Started by metalman666 Sep 14, 2010 at 12:07 AM 4 replies 1.4k views
Original Post
metalman666
metalman666
Hello guys,

I know the code below is bad, but i need to know why is this even working. Please read the code.

#include <iostream>class Dumb{    int x;    int y;public:    const int getX(){ return x; }    const int getY(){ return y; }};int main(void){      Dumb* pDumb = new Dumb();    int* var = (int*)pDumb;    (*var) = 10;    std::cout << "\nDumb::x : " << pDumb->getX();    std::cout << "\nDumb::y : " << pDumb->getY();       delete pDumb;    return 0;}Output::Dumb::x : 10Dumb::y : 0




Question:
1. Why line {int* var = (int*)pDumb;} is working?
2. Why line {pDumb->getX();} shows the output as 10?
3. Why line {pDumb->getY();} shows the output as 0?
4. How can a integer pointer assign value to a private variable?

My environment: Visual Studio Team Edition 2008, Windows XP

Thanks for any explanation.
fastcall22
fastcall22
Quote:
Original post by metalman666
1. Why line {int* var = (int*)pDumb;} is working?


When you allocate a Dumb via new, the pointer returned is the memory allocated for the object's members. On some specific compilers and some special circumstances, the pointer points to the first member variable of the object. The statement reinterpret_casts pDumb to an integer array (i.e. The pattern of 1s and 0s located at the address pDumb points to will now be viewed as an array of integers).

Quote:

2. Why line {pDumb->getX();} shows the output as 10?


Since var == pDumb == &(pDumb->x), writing to var consequently overwrites pDumb->x (on some specific compilers and/or special circumstances!)

Quote:

3. Why line {pDumb->getY();} shows the output as 0?


It outputs 0 because you haven't touched it... yet. Now try this:
*(var+1) = 5; // Oops, I trampled over your pDumb->y

Quote:

4. How can a integer pointer assign value to a private variable?

You don't actually point to a private variable; it just so happens that the Dumb object pointer points to a private variable. (In other words, offsetof(Dumb,x)==0)

Now try the code below:

#include <iostream>using namespace std;class Hello {public:	virtual ~Hello() {	}	virtual void foobar() {	}};class Dumb : public Hello{	int x;	int y;public:	const int getX(){ return x; }	const int getY(){ return y; }	void foobar() {	}};int main(void){  	Dumb* pDumb = new Dumb();	int* var = (int*)pDumb;	(*var) = 10;  // offsetof( Dumb, x ) is 4; this line of code is trashing the object's vtable!	(*var+1) = 5;	cout << "\nDumb::x : " << pDumb->getX();	cout << "\nDumb::y : " << pDumb->getY();	delete pDumb;  // Boom! You broke it.	return 0;}// Output before crashing:Dumb::x : -842150451Dumb::y : -842150451
metalman666
metalman666
@_fastcall
Well this line of code beats me.
*(var+1) = 5;

This correctly assigned y to 5. Any explanation.

I tried the code with inheritance and it did crashed, but the new output is now

Dumb::x : 5
Dumb::y : -842150451

not as you wrote

Dumb::x : -842150451
Dumb::y : -842150451

Again Why?

This really spins my head.
Decrius
Decrius
Quote:
Original post by metalman666
@_fastcall
Well this line of code beats me. *** Source Snippet Removed ***
This correctly assigned y to 5. Any explanation.

I tried the code with inheritance and it did crashed, but the new output is now

Dumb::x : 5
Dumb::y : -842150451

not as you wrote

Dumb::x : -842150451
Dumb::y : -842150451

Again Why?

This really spins my head.


Yes, because *(var) is the vtable (apparently), *(var + 1) is 4 bytes further (with sizeof int == 4). That is probably exactly where 'x' is located. *(var + 2) then gets you to another 4 bytes further, giving probably 'y'.

I say probably, because in C++ a lot of this stuff is "undefined" or compiler / OS specific. So you might not get the same results on all machines.

Whether the variables are const or private doesn't matter, you circumvent the typing system, by C-casting from Anything* to int*, you make the compiler believe it's an array of integers it's looking at, not a structure with different data types in sequence (though in this case, the class _only_ holds two integers in memory, and perhaps the vtable).

So this is just ordinary type-unsafe C-style memory hacking.


EDIT: You really need to think in terms of memory, not the code that defines it. What the class looks like in memory is as follows:

Dumb:|--------|| int x; | offset 0|--------|| int y; | offset 4|--------|


So if you make the compiler believe Dumb* is actually an int*, it will happily (or silently) believe you. And with the particular layout of Dumb as above, it even holds true.

The following data types hold exactly the same memory (given the layout as above for Dumb):

struct Dumb{    int x;    int y;};Dumb *dumb = new Dumb;dumb->x = 5;dumb->y = 10;int *array = new int[2];array[0] = 5;array[1] = 10;// swap themDumb *tmp = dumb;dumb = (Dumb *) array;array = (int *) tmp;assert(dumb->x == array[0]);assert(dumb->y == array[1]);


Remember, *(array + i) equals array.

Also, I'd advice against C-style casting. Better use static / reinterpret casts.
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
rip-off
rip-off
Its implementation specific, but here is why.
*(var+1) = 5;

If you are not familiar, that is pointer arithmetic. It is roughly equivalent to treating the "var" pointer as an array of integers, then setting the second element of that array to 5. In this case, the tightly packed members of the "Dumb" class correspond to contiguous array elements, resulting in "y" being at the same position as the second element of this array. Hence the assignment.

Quote:

Dumb::x : 5
Dumb::y : -842150451

This output is caused because the compiler you are using appears to implement virtual methods using the "vtable" approach (in practise, pretty much all compilers do this). This means that a class with at least one virtual method will feature a hidden pointer as (typically) the first member (in multiple inheritance it gets more complicated). This pointer points at a table which includes all virtual functions that this class uses. So the base class table would include the base virtual functions, and a derived class might have one or more of these function pointers pointing at its own implementations.

This means that when *var = 10 is executed, the vtable pointer is overwritten with the value 0xA, which generally is not a valid pointer. The next line overwrites the second element of the array, which is now the "x" member, with 5. When the "delete" line is executed, the virtual destructor is invoked. This involves dereferencing the vtable entry, and the invalid vtable pointer triggers a segmentation fault.

Again, this is all implementation specific and you shouldn't rely on it. In standardese you invoke undefined behaviour, the standard doesn't mandate that the implementation behave in any sane or even deterministic manner.

Here is an example where I overwrite the vtable pointer with something that acts like a vtable (an array of pointers to functions).
#include <iostream>using namespace std;void not_a_destructor() {   cout << "not a destructor!" << endl;}typedef void (*function_ptr)();class Hello {public:	virtual ~Hello() {	}};class Dumb : public Hello{	int x;	int y;public:	const int getX(){ return x; }	const int getY(){ return y; }};int main(void){  	Dumb* pDumb = new Dumb();	int* var = (int*)pDumb;        function_ptr vtable[2] = { 0, ¬_a_destructor };	(*var) = (int)&vtable	cout << "\nDumb::x : " << pDumb->getX() << endl;	cout << "\nDumb::y : " << pDumb->getY() << endl;	delete pDumb;  // Boom! You broke it.	return 0;}

This doesn't crash, and outputs:
Quote:


Dumb::x : -1819044973

Dumb::y : -1819044973
not a destructor!

Note that it was trial and error for me to discover that the destructor appears in the second slot in the vtable, the first slot might be reserved for a pointer to some runtime type information. There is no way to tell without reading about a specific compiler's implementation.


You should note that "private" is just a compile time defense. If you do something bold to obtain a pointer to a member, it can be changed.
metalman666
metalman666
Thanks a lot. All of you. Much appreciated. I now see what was happening.

I can sleep now.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.