通过C++代码验证进程中的内存布局

概述

前阵子看《C++应用程序性能优化》,其中提到进程中的内存布局(以前只是知道内存中有这几个不同的区域,但并没有深入了解它们的分布位置)。为了加深理解,决定还是写个代码验证一下。

《C++应用程序性能优化》中谈到的内存布局大致如下(以Linux系统为例)


图片很清晰明了,不过有个问题,Linux下分布是这样的,那Windows下面有没有什么差异呢?上网查了下,看到别人贴的Windows进程中的内存布局,大致和Linux下也没特别大的不同,内核区、代码区、全局区、堆、栈至少在位置分布上是一致的:


好了,以上是理论基础,接下来写个程序验证一下吧!

硬件:Intel64位CPU

系统:WIN7 64位系统

软件平台:WIN32控制台程序(32位编译器)


1. 首先验证下全局变量和静态变量

// 全局变量
int nGlobalValue = 1;
double dGlobalValue = 2;

int _tmain(int argc, _TCHAR* argv[])
{
	// 静态变量
	static char cStatisValue = 2;
	printf("&nGlobalValue:		0x%x\n", &nGlobalValue);
	printf("&dGlobalValue:		0x%x\n", &dGlobalValue);
	printf("&cStatisValue:		0x%x\n", &cStatisValue);
	printf("\n");
	getchar();

	return 0;
}

执行结果:



从以上代码可以发现两个现象

(1) 前两个全局变量地址之间间隔了8个字节:

可是nGlobalValue明明是个int型啊,应该只占用了4个字节,为什么不是在int变量的地址之后4个字节,开始存放double变量呢?原因就是内存对齐。

为什么要做内存对齐呢?

因为CPU从内存中取数据是以固定长度来取的,譬如以上代码示例是在64位CPU上运行的,CPU取数据会按8字节(64位)长度来取。代码中第二个全局变量是double型,假如它的地址是在0x16004开始的,那么CPU获取该double变量需要经过两次取数据操作:首先从0x16000地址取8个字节,获取它的高32位放入dGlobalValue变量的低32位,然后从0x16008地址取8个字节,获取它的低32位放入dGlobalValue变量的高32位。如果加了内存对齐,让double型变量都从8倍数的地址开始分配,CPU就只需要执行一次取数据的操作就可以了。因此基于效率考虑,操作系统会做内存对齐,dGlobalValue从0x16008地址开始分配。

(2)静态变量的地址夹在两个全局变量中间

这正好说明全局变量和静态变量都是放在全局数据区,它们之间没有分界;还有就是编译器会做内存优化,把后分配的char型变量放到了0x16004,充分利用了空闲的内存空间,防止内存浪费。


2. 接下来增加字符串常量

// 全局变量
int nGlobalValue = 1;
double dGlobalValue = 2;

int _tmain(int argc, _TCHAR* argv[])
{
	// 静态变量
	static char cStatisValue = 2;
	printf("&nGlobalValue:		0x%x\n", &nGlobalValue);
	printf("&dGlobalValue:		0x%x\n", &dGlobalValue);
	printf("&cStatisValue:		0x%x\n", &cStatisValue);
	printf("\n");

	// 常量字符串
	const char *pLocalString1 = "hello";
	const char *pLocalString2 = "world";
	printf("pLocalString1:		0x%x\n", pLocalString1);
	printf("pLocalString2:		0x%x\n", pLocalString2);
	printf("&pLocalString1:	0x%x\n", &pLocalString1);
	printf("&pLocalString2:	0x%x\n", &pLocalString2);
	printf("\n");

	getchar();
	return 0;
}

执行结果


pLocalString1和pLocalString2是常量字符串的地址,从内存地址看,和全局区的内存地址相隔比较近,但又不是连续的。说明常量数据区和全局数据区很可能是挨着的。&pLocalString1和&pLocalString2是存放字符串地址的指针,从代码来看很显然是存放在栈上。由此可见,栈内存与全局、常量数据区内存之间相隔比较远。


接下来稍微修改一下:(把pLocalString2字符串改成和pLocalString1一样的,同时创建另外一个字符串数组,其存储的字符串也设置成"hello")

// 全局变量
int nGlobalValue = 1;
double dGlobalValue = 2;

int _tmain(int argc, _TCHAR* argv[])
{
	// 静态变量
	static char cStatisValue = 2;
	printf("&nGlobalValue:		0x%x\n", &nGlobalValue);
	printf("&dGlobalValue:		0x%x\n", &dGlobalValue);
	printf("&cStatisValue:		0x%x\n", &cStatisValue);
	printf("\n");

	// 常量字符串
	const char *pLocalString1 = "hello";
	const char *pLocalString2 = "hello";
	char cLocalString[] = "hello";
	printf("pLocalString1:		0x%x\n", pLocalString1);
	printf("pLocalString2:		0x%x\n", pLocalString2);
	printf("&pLocalString1:	0x%x\n", &pLocalString1);
	printf("&pLocalString2:	0x%x\n", &pLocalString2);
	printf("cLocalString:		0x%x\n", cLocalString);
	printf("\n");

	getchar();
	return 0;
}
执行结果:



发现没,pLocalString2和pLocalString1地址是一样的,也就是说同一常量字符串在内存中只会有一份数据,而不是每个变量都有各自的副本。查资料可知,字符串常量存放的地方叫字符串字面量池,目的就是保证多次用到同一字面量时,在内存中只有一个副本。

新增的cLocalString变量,从内存地址看,它是在栈上分配的,并不是直接指向常量数据区。

从上面代码看,栈内存也是连续分配的,而且地址从高往低分配。


3. 接下来从堆上申请内存

// 全局变量
int nGlobalValue = 1;
double dGlobalValue = 2;

int _tmain(int argc, _TCHAR* argv[])
{
	// 静态变量
	static char cStatisValue = 2;
	printf("&nGlobalValue:		0x%x\n", &nGlobalValue);
	printf("&dGlobalValue:		0x%x\n", &dGlobalValue);
	printf("&cStatisValue:		0x%x\n", &cStatisValue);
	printf("\n");

	// 常量字符串, 栈内存
	const char *pLocalString1 = "hello";
	const char *pLocalString2 = "hello";
	char cLocalString[] = "hello";
	printf("pLocalString1:		0x%x\n", pLocalString1);
	printf("pLocalString2:		0x%x\n", pLocalString2);
	printf("&pLocalString1:	0x%x\n", &pLocalString1);
	printf("&pLocalString2:	0x%x\n", &pLocalString2);
	printf("cLocalString:		0x%x\n", cLocalString);
	printf("\n");

	// 堆内存
	int *pNewInts = new int[3];
	short *pNewShort = new short;
	printf("pNewInts:		0x%x\n", pNewInts);
	printf("pNewShort:		0x%x\n", pNewShort);
	printf("&pNewInts:		0x%x\n", &pNewInts);
	printf("&pNewShort:	0x%x\n", &pNewShort);
	printf("\n");

	getchar();
	return 0;
}

执行结果


&pNewInts和&pNewShort是指针的地址,同样也是栈上分配的,和前面几块栈内存是连续的。pNewInts和pNewShort就是从堆上申请的内存,从地址来看,与全局区、常量区、栈内存都不接近。而且两块堆内存的地址也并不连续(因为堆内存是通过链表管理的,从空闲堆链表中找到满足条件的内存块来分配,并不保证先后分配的两块内存一定会是连续的;而且堆内存包含记录内存大小以及链表节点的头,所以实际上占用的内存大小比实际申请的大小要大)。


根据以上分析可以判断,内存区中:全局/静态区、常量区、栈、堆这几个内存分块的结论是成立的。但是同时也发现一个问题,这几个分块通过代码执行的结果来看,和上面图片展示的分布位置并不一致,这是为什么呢?后续再探讨下,有知道的朋友也可以留言指教。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值