visual c ++_Visual C ++对象内存布局

visual c ++

In our object-oriented world the class is a minimal unit, a brick for constructing our applications. It is an abstraction and we know well how to use it. In well-designed software we are not usually interested in knowing how objects look in memory.

在我们面向对象的世界中,类是一个最小的单元,是构建我们的应用程序的一块砖。 这是一个抽象,我们很清楚如何使用它。 在设计良好的软件中,我们通常对了解对象在内存中的外观通常不感兴趣。

Sometimes the understanding of the memory structure of the C++ class may help in the modern design.

有时,对C ++类的内存结构的理解可能有助于现代设计。

I have to mention that I work with Microsoft Visual Studio and all code posted here was written for this compiler. More then it, I guess that the object memory layout can be different from the one I'm showing here, if you use another compilers. Some "tricks" you will see here are dangerous enough to be used in the production code. It is good to know, nice to understand, but to use such tricks in the real projects... from my point of view it smells very bad.

我不得不提到我使用Microsoft Visual Studio,并且这里发布的所有代码都是为此编译器编写的。 而且,如果您使用其他编译器,我想对象内存布局可能与我在此处显示的布局有所不同。 您将在此处看到的一些“技巧”足够危险,无法在生产代码中使用。 很高兴知道,也很高兴理解,但是在实际项目中使用这样的技巧……从我的角度来看,它很难闻。

The following application shows the size of few a trivial objects:

以下应用程序显示了一些琐碎的对象的大小:

#include <Windows.h>
#include <stdio.h>

class CEmpty
{
};

class C1Int
{
	int x;
};

class C2Int
{
	int x;
	int y;
};

class C3Int
{
	int x, y, z;
};

class CInt
{
	int x;
public:
	CInt() : x(0) {}
};

class CIntDerived: public CInt
{
	int y;
public:
	CIntDerived() : y (0) {}
};

class CCharArray
{
	char x[24];
};

class CStr
{
	char x[24];
public:
	CStr()
	{
		strcpy(x, "Hello");
	}
};

int main()
{
	CEmpty empty;

	C1Int one;
	C2Int two;
	C3Int three;

	CInt simple;
	CIntDerived derived;

	CCharArray charArray;
	CStr str;

	printf("Size of CEmpty object is %d\r\n", sizeof(empty));

	printf("Size of C1Int object is %d\r\n", sizeof(one));
	printf("Size of C2Int object is %d\r\n", sizeof(two));
	printf("Size of C3Int object is %d\r\n", sizeof(three));

	printf("Size of CInt object is %d\r\n", sizeof(simple));
	printf("Size of CIntDerived object is %d\r\n", sizeof(derived));

	printf("Size of CCharArray object is %d\r\n", sizeof(charArray));
	printf("Size of CStr object is %d\r\n", sizeof(str));

	return 0;
}
Size of the objectsAs you see the object of the absolutely empty class takes 1 byte in the memory - exactly as it states in the standard - an object of the empty class should occupy 1 byte. Because it is an object and it must have a size. When such empty class is inherited, the standard allows to optimize it away.

Object of the class with only one integer as a member has size 4 bytes. Objects with 2 or 3 integers inside use 8 and 12 bytes accordingly. (Size of the integer type is platform/compiler specific and will vary on diffrent platforms and compilers because the standard does not specify the size of any integral type)

只有一个整数作为成员的类对象的大小为4个字节。 内部带有2或3个整数的对象分别使用8和12个字节。 (整数类型的大小是特定于平台/编译器的,并且在不同的平台和编译器上会有所不同,因为该标准未指定任何整数类型的大小)

The last declared class with an array as the member-variable has the same size as the array. The class methods do not change the size.

最后声明的带有数组作为成员变量的类具有与数组相同的大小。 类方法不会更改大小。

All shown classes have only private members, but all these members can be printed out:

所有显示的班级只有私人成员,但是所有这些成员都可以打印出来:

#include <Windows.h>
#include <stdio.h>

class C1Int
{
	int x;
public:
	C1Int() : x(0) {}
};

class C2Int
{
	int x;
	int y;
public:
	C2Int() : x(1234), y(5678) {};
};

class CStr
{
	char x[24];
public:
	CStr()
	{
		strcpy(x, "Hello");
	}
};

int main()
{
	C1Int one;
	C2Int two;
	CStr str;

	printf("C1Int::x = %d\r\n", *(int*)&one);

	printf("C2Int::x = %d\r\n", *(int*)&two);
	printf("C2Int::y = %d\r\n", *((int*)&two + 1));

	printf("CStr::x = %s\r\n", (char*)&str);

	return 0;
}
Private membersAn even modify them:
#include <Windows.h>
#include <stdio.h>

class CInts
{
	int x;
	int y;

public:
	CInts() : x(1234), y(5678) {};
	void out()
	{
		printf("CInts::x = %d\r\n", x);
		printf("CInts::y = %d\r\n", y);
	}
};

int main()
{
	CInts one;
	one.out();

	//the following 2 lines are very dangerous and may cause a crash if you'll
	//try to use this style in your projects
	*(int*)&one = 111;
	*(((int*)&one) + 1) = 222;
	one.out();

	return 0;
}
Modification of the private membersIn these examples above you see that the object address is the address of the first member-variable of the class. You can see it also in assembler, if you'll stop the debugging of the following example and switch to the assembler window:
AssemblerThe out() method prints both internal class members and you see how both of these variables are pushed into the stack - the address of the first variable is the same as of the class object and the second variable follows in the memory:
MemoryWhat we see in this memory block (CInts object) is just a structure:
struct ints { int x; int y; }
#include <Windows.h>
#include <stdio.h>

class CStr
{
	char text[32];

public:
	CStr(char* psz)
	{
		text[0] = 0;
		if (psz != NULL)
			strncpy(text, psz, 30);
	};
};

int main()
{
	CStr str("Hello");
	printf("str = %s\r\n", &str);

	strcpy((char*)(&str), "bye");
	printf("str = %s\r\n", &str);
	return 0;
}
StringAll shown is correct untill the virtual functions do not appear on the scene.

The following application shows that even the class that has nothing but only one virtual method takes 4 bytes in the memory.

以下应用程序显示,即使只有一个虚拟方法的类也只占用4个字节的内存。

#include <Windows.h>
#include <stdio.h>

class CEmpty
{
public:
	virtual void out() 
	{
		printf("CEmpty\r\n");
	}
};

class C1Int
{
	int x;
public:
	C1Int() : x(0) {}
	virtual void out_x() 
	{
		printf("x = %d\r\n", x);
	}
};

class C2Int
{
	int x, y;
public:
	C2Int() : x(0), y(0) {}
	virtual void out_x() 
	{
		printf("x = %d\r\n", x);
	}
	virtual void out_y() 
	{
		printf("y = %d\r\n", y);
	}
};

class CIntDerived: public C1Int
{
	int y;
public:
	CIntDerived() : y (0) {}
	virtual void out_y() 
	{
		printf("y = %d\r\n", y);
	}
};

int main()
{
	CEmpty empty;
	C1Int one;
	C2Int two;
	CIntDerived derived;

	printf("Size of CEmpty object is %d\r\n", sizeof(empty));
	printf("Size of C1Int object is %d\r\n", sizeof(one));
	printf("Size of C2Int object is %d\r\n", sizeof(two));
	printf("Size of CIntDerived object is %d\r\n", sizeof(derived));

	return 0;
}
Objects with virtual methodsSize of CEmpty object (the name is already wrong) is 4 bytes now. Objects of C1Int, C2Int, CIntDerived classes also added 4 bytes. For the Microsoft developers this is a well-known number - size of the integer. It seems like a pointer was added into the memory of these objects.

Let's investigate it in the debug mode. Stop the debugging of the following application and check the object in the memory window (type &str in the edit box there), we will find these 4 bytes - "Hello" is not in the beginning of the object memory. It moved for 4 bytes. And the first 4 bytes are the pointer that we didn't see in the previous examples. It can be only a pointer to the virtual method table.

让我们在调试模式下进行研究。 停止以下应用程序的调试,并在内存窗口中检查对象(在此处的编辑框中键入&str),我们将发现这4个字节-“ Hello”不在对象内存的开头。 它移动了4个字节。 前4个字节是我们在前面的示例中未看到的指针。 它只能是指向虚拟方法表的指针。

#include <Windows.h>
#include <stdio.h>

class CStr
{
	char text[32];

public:
	CStr(char* psz)
	{
		text[0] = 0;
		if (psz != NULL)
			strncpy(text, psz, 30);
	}

	virtual void out()
	{
		printf("text = %s\r\n", text);
	}
};

int main()
{
	CStr str("Hello");
	str.out();
	return 0;
}
Pointer to virtual method tableThis table also can be investigated in the same way as was used for the class internal variables:
#include <stdio.h>

class C1Int
{
	int x;

public:
	virtual void test()
	{
		printf("C1Int::test()\r\n");
	}
};

class C2Int : public C1Int
{
	int y;

public:
	virtual void test()
	{
		printf("C2Int::test()\r\n");
	}

	virtual void test1()
	{
		printf("C2Int::test1()\r\n");
	}

	virtual void test2()
	{
		printf("C2Int::test2()\r\n");
	}
};

int main()
{
	C2Int two_int;
	printf("size of C2Int object is %d\r\n", sizeof(two_int));
	printf("Address of C2Int object is %p\r\n", &two_int);

	int* p = (int*)(&two_int);
	printf("Address of C2Int pointer is %p\r\n", p);

	int p1 = *((int*)(&two_int));
	printf("Value of this pointer is %x\r\n", p1);

	int* _p1 = (int*)*((int*)(&two_int));
	printf("Same..        pointer is %p\r\n", _p1);

	int* p2_1 = (int*)*((int*)*(int*)(&two_int));
	printf("Value of first entry of C2Int VTable is %p\r\n", p2_1);

	int* p2_2 = (int*)*((int*)*(int*)(&two_int) + 1);
	printf("Value of second entry of C2Int VTable is %p\r\n", p2_2);

	int* p2_3 = (int*)*((int*)*(int*)(&two_int) + 2);
	printf("Value of third entry of C2Int VTable is %p\r\n", p2_3);

	int* p2_4 = (int*)*((int*)*(int*)(&two_int) + 3);
	printf("Value of fourth entry of C2Int VTable is %p\r\n", p2_4);

	int* p2_5 = (int*)*((int*)*(int*)(&two_int) + 3);
	printf("Value of fifth entry of C2Int VTable is %p\r\n", p2_5);

	return 0;
}
Virtual Method TableThe table from the example above has 3 entries, the following entry is NULL. So it is possible to count how many virtual methods are in an object. Thats firstly. Secondly, we can call the methods in a strange way:
#include <stdio.h>

class C1Int
{
public:
	virtual void test()
	{
		printf("C1Int::test()\r\n");
	}
};

class C2Int : public C1Int
{
public:
	virtual void test()
	{
		printf("C2Int::test()\r\n");
	}

	virtual void test1()
	{
		printf("C2Int::test1()\r\n");
	}

	virtual void test2()
	{
		printf("C2Int::test2()\r\n");
	}
};

typedef void (* func)();

int main()
{
	C2Int two_int;

	int* p = (int*)(&two_int);
	printf("Address of C2Int pointer is %p\r\n", p);

	int p1 = *((int*)(&two_int));
	printf("Value of this pointer is %x\r\n", p1);

	int* p2_1 = (int*)*((int*)*(int*)(&two_int));
	printf("Value of first entry of C2Int VTable is %p\r\n", p2_1);

	int* p2_2 = (int*)*((int*)*(int*)(&two_int) + 1);
	printf("Value of second entry of C2Int VTable is %p\r\n", p2_2);

	int* p2_3 = (int*)*((int*)*(int*)(&two_int) + 2);
	printf("Value of third entry of C2Int VTable is %p\r\n", p2_3);

	func f1 = (func)p2_1;
	f1();

	f1 = (func)p2_2;
	f1();

	f1 = (func)p2_3;
	f1();

	C1Int one_int;
	func f2 = (func)((int*)*((int*)*(int*)(&one_int)));
	f2();

	return 0;
}
Virtual methodsThirdly, with a small trick we can replace a virtual method in an object without permission:
#include <stdio.h>
#include <memory>

class CBase
{
public:
	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CVirtTest
{
public:
	virtual void test()
	{
		printf("CVirtTest::test()\r\n");
	}
};

typedef void (* func)();
typedef struct 
{
	func Entry;
} FUNC_TABLE;

void Trick()
{
	printf("Trick()\r\n");
}

int main()
{
	CVirtTest test;
	CBase* pBase = (CBase*)&test;

	FUNC_TABLE funcTable  = { Trick };
	FUNC_TABLE* pFuncTable  = &funcTable;
	//dangerous code, don't use it in your projects.
	memcpy(&test, &pFuncTable, sizeof(long));

	pBase->test();
	return 0;
}
ReplacementSo I can say that for the Microsoft compiler, an object of a C++ class in the memory is a continuous memory buffer that contains class member-variables and, optionally, a pointer to to the virtual method table. This virtual method table is presented if the class contains at least one virtual method.

I think the following pseudocode illustrates everyhting I was talking about:

我认为以下伪代码说明了我所谈论的一切:

An object of the class declared as:

该类的对象声明为:

class ClassStructure
{
    int x;
    int y;
public:
   virtual void nothing() {}; 
}
struct ClassStructure
{
    void* m_pVptr;
    int x;
    int y;
}

Wikipedia: C++ classes.

维基百科:C ++类。

Memory Layout for Multiple and Virtual Inheritance.

多重和虚拟继承的内存布局。

or in this book:

或在这本书中:

Steve Oualline. Practical C++ Programming, Second Edition.

史蒂夫·夸林(Steve Oualline)。 实用C ++编程,第二版。

翻译自: https://www.experts-exchange.com/articles/1242/Visual-C-Object-Memory-Layout.html

visual c ++

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值