cpp关于对象、对象数据成员的地址和空间分配的探究(包括:main函数内定义的对象、全局对象、函数参数对象、函数参数对象及函数内定义的局部对象、动态new的对象)

①main函数里定义2个对象,看一下各数据成员(静态/非静态)的地址和存储空间

类定义如下:

成员函数实现如下:(都是简单返回地址的值)

实现输出地址:

(注意:对于返回char*类型的数据,在cout输出时,必须(void*)强制类型转换一下,否则cout输出的是这个char*指针指向的字符串而非char*本身这个地址。[另外的解决方法:用printf]。原因详见:http://t.csdn.cn/9lo6Y

输出结果:

【分析】:

Test类中含有6个数据成员(1静态static+5非静态)。

首先看到,Test类的两个实例化对象a、b对应的静态变量t0地址相同---“本类对象共用同一份静态数据成员的拷贝”

然后,对象a、b所占存储空间为24字节,但实际上数据成员所占空间之和为:4+2+8+1+1 = 16bytes,原因接下来讲。

再看对象a的5个非静态数据成员,不同类型所占字节数如下:int--4bytes,short--4bytes,double--8bytes,char--1byte。发现大部分数据和预想的空间占用相符,唯有 应当是2字节的short类型却占了4字节,和预想的不同,原因是“字节对齐”(目的是加快寻址速度,因此会浪费一点空间,是权衡的结果。详见:http://t.csdn.cn/41TCa,下图为核心部分)

于是我们尝试把类中的数据成员顺序换一下,希望能避免“字节对齐”导致的空间损失,修改后如下:

再运行,结果如下:

果然,避免了“字节对齐”。

(另外说明:不同数据类型的变量(对象)在程序执行时 具体所占多少字节,与:①c++标准规定的数据"最小尺寸";②32/64位机;③不同编译器 有关,此外c++语言还规定:无论怎样设置,都应遵循(size:)short <= int <= long <= long long。下面是GCC编译器在32/64位机上各种数据类型的占用空间)

②定义一个全局对象,看看地址

还是用原先的数据成员顺序定义类:

main函数外部定义全局对象:

输出地址的实现:

结果:

【分析】:

全局对象的地址紧挨着静态数据成员的地址,因此它们都存放于“静态区”。

给对象分配空间-最小单位是0x000010个字节(即:16bytes);对象内部给数据成员分配空间-最小单位是0x000008个字节(即:8bytes)。

【原因】:

一方面,静态成员变量t0是int类型,只占4字节,但global_X直接从t0那里垮了16个字节而非 8字节

(

对象内部各数据成员之间都是跨8字节 e.g. 看对象a或b的2.short(1.int)首地址 -> 3.double首地址 以及 对象a或b的t_char_1首地址 -> 最后返回的“对象实际占用空间24bytes”

对象之间是跨16字节 e.g. 看对象a、b的“所占存储空间”(24bytes) 和他们首地址的跨度(0x000020==32bytes),如果是“给对象的空间分配最小单元”是8字节,那么首地址跨度应该也为24bytes

)

【注意:事实上 cpp中没有“变量”,只有对象。这个静态成员t0-是int类型的对象(而且,由于它和其它非静态数据成员不同,不从属于某个对象,所以不被视为“数据成员”,而是作为int类型的“变量”(对象)存放在static静态区,因此global_X在t0之后分配空间的时候,垮了16个字节而非8个。)】

另一方面,做了个小测试,我在global_X之前定义了个全局对象global_Y:

此时再看global_X的地址,如下:

和上一次的0x408040相比,global_X的首地址被挤的后移了0x000020==32bytes,但仔细看数据成员所占空间(global_Y和global_X相同),是:4+2(+2)+8+1+1==16+2 [(+2)是short之后“字节对齐”的空间消耗]

因此,由于多的这2bytes(准确说是global_Y在0x408050和0x408051这2bytes空间),导致下一个静态区中的对象需要从0x408060开始分配(0x408052~0x40805f这14bytes的空间直接被跳过)

静态变量t0地址和之前的对象a、b相同---“本类对象共用同一份静态数据成员的拷贝”

③外部函数-其中形参是对象,内部也定义局部对象。看看这些对象的地址

外部函数定义如下:(没有自定义“拷贝构造函数”,用的是默认的)

主函数中调用:

结果:

【分析】:

都和之前main函数中直接定义的对象a、b地址相近。因此,“外部函数的参数对象&外部函数中定义的局部对象”都和main函数中定义的对象一样 存放在堆栈stack里。(因为堆栈中的变量(对象)生成次序通过对比发现 应该是倒序,所以“地址绝对值”越大,越是先生成。所以和预想的一样--【生成次序】:main函数中定义的实参 --> 函数形参传输后自动生成的局部对象 --> 函数内部再定义的局部对象

唯一奇怪的点是:传入的实参x所生成的局部对象地址为0x61fd90,而函数中一些(不会产生新变量(对象))的cout输出操作之后,紧接定义的局部对象q 它的地址是0x61fd30(和前一个相隔了0x000060==96bytes==3*[对象空间]),按理说应该只相隔1个对象空间==0x000020==32bytes。【就是说在参数对象x定义分配空间之后、局部对象q定义分配空间之前,还有两个同类型对象被定义并分配空间了,即使我们在函数体的第一句就定义局部对象q】

下面是 尝试把cout那一段注释掉,使得传入实参x之后直接就定义局部对象q,但结果没有变化。彻底排除了 cout这段语句创造了某些变量(对象)占用空间 的可能。

【结论】:

猜测应该是函数中的“临时变量” 【感觉应该是 ①负责传参的②负责局部变量赋值的 各有 一个临时变量对应,且非必要不定义(编译器根据用户在本函数中有没有用到,来判断是否创建)】

依据:

①关于“非必要不定义”:当函数不再需要传入对象参数的时候,发现他们的地址“距离”靠近了,插值正好是 0x000020==32bytes==1个对象空间 ---> 说明:负责“传参”的临时变量没有被定义,节省了1个对象空间

②关于“负责 局部变量赋值的临时变量最多只需要一个”:尝试定义多个局部变量,对结果没影响

(也好理解:整个程序不存在并行,因此同一临时变量可以反复多次使用,逐个的初始化对象。所以 “负责局部变量赋值的临时变量只需要一个”)

③关于“负责 传参的临时变量,按参数个数分配”:尝试增加参数个数,结果有变化---增加了2个对象参数,结果q的地址又“远离”了2个对象空间大小(从原先的0x61fd30 --> 0x61fcf0 插值=0x000040==64bytes==2个对象空间)

④main函数中动态new的对象

【分析】:

①动态new的对象存放的地址和之前的对象存储区域(stack、static)都不同,证明了它被存放在第三个区域(heap)

②delete之后,我们发现,虽然对象已经消失、空间已经被释放,但指针所指的地址没有任何变化。

体现出:delete是顺着指针去进行释放空间的操作,但并不会对指针的值进行改变。

【同时,发现仍然可以“按照对象(类)的格式(数据成员的分布)去访问”。这体现出“访问一个对象的某个数据成员”背后的机制,即:相应类型的对象指针只负责找到该对象的初始地址,找到初始地址之后,就根据我们(内存中所存的)类的具体格式,来给(这一整块大的)对象空间进行“分割”,从而明确--我们该具体在哪个地址找该对象的相应数据成员

③对比空间被释放前后,发现:“delete前后输出的t_int值”立刻不一样(但所指的地址没变“②”) && 单次程序执行中100s内多次输出结果相同(值改变后就不再变了) && 多次程序执行的结果不同

可能的原因:

  1. delete之后系统自动给它改了个随机值(赋值完成后才继续执行接下来的程序),但暂时没有分配给其他的进程使用,因此我们看到值被修改后就不再变;

  1. delete之后系统把它立即分配给某个进程,进程也立即对这块空间进行写入(而且速度比我们程序执行的速度要快--因为我们是在delete之后立即cout输出的,但多次执行程序从来没有得到过“原值(123)”),并且(在新进程的控制下)长时间不再修改这块空间存的数据。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值