您的位置:首页 > 编程语言 > C语言/C++

C++类、结构对象内存布局浅析

2015-07-17 17:22 513 查看
最近面试多,出的题目很多都有如下形式,给定一个class或者struct的定义,例如这样:

[cpp] view
plaincopy

struct node {

int a;

char b;

int c;

char d;

};

问题是:sizeof(node) = ?

之前了解过对齐的概念,但是不深入,所以在这里自己做了一些小测试,说一下自己的看法。先告诉大家吧,上面那题答案是16。

如果用“对齐”的说法,那么就是先找到这个struct中占内存最大的一个类型——“int”,然后简单地进行“变量个数 * sizeof(int) = 4 * 4 = 16”即可。但是这样的做法其实有点问题,下面会继续探讨。

先上一段代码(可以自己先想一下这段代码的输出是什么?):

[cpp] view
plaincopy

#include<iostream>

#include<stdio.h>

using namespace std;

struct node {

int a;

char b;

int c;

char d;

node(){a=1,b=2,c=3,d=4;}

};

int main()

{

node n;

printf("a:%d b:%d c:%d d:%d\n", n.a, n.b, n.c, n.d);

void *p = &n;

*((int*)p) = 12345;

cout<<n.a<<endl;

p = ((int*)p) + 1;

*((char*)p) = 107;

cout<<(int)n.b<<endl;

p = ((char*)p) + 1;

*((int*)p) = 0x80000000;

cout<<(unsigned int)n.c<<endl;

cout<<(int)n.d<<endl;

cout<<"size:"<<sizeof(node)<<endl;

int stop;

cin>>stop;

}

代码输出:

[html] view
plaincopy

a:1 b:2 c:3 d:4

12345

107

128

4

size:16

解析如下(以下所有分析仅为本人推测所得,实际是否如此请参见更权威的资料):

void *p = &n这句,使得p指针指向了n的起始地址,然后我们将void*类型的指针转换为int*类型,并对其赋值12345,然后发现a变成了12345,说明a的首地址和n的首地址是相同的。

接着我们让指针p移动一个int的距离,向高地址方向移动,如果猜测没错,现在应该移动到了b的首地址,那么赋值操作将会使b的值变为107,输出也验证了这个猜测。

接下来就是重点了,我们让p移动一个char大小的距离(即1个字节),如果struct内的成员变量是密集排布的,那么现在应该移动到了c的位置,赋值0x80000000,换成二进制就是1000 0000 0000 0000 0000 0000 0000 0000,即便int的最小值-2147483648。然后输出c的值时发现其等于128。

什么时候int的值是128?只有其二进制为0000 0000 0000 0000 0000 0000 1000 0000的时候。红色字的部分就是被改变的部分,也就是说只有低地址的一个字节(8bit)被赋值了,另外的24bit内容丢失了吗?结合之前所说的“对齐”问题,那么就可以大胆猜测其实b和c之间隔了3个字节,所以进行赋值的时候,低位的24个bit作用在变量b后面的3个字节中,而高位的1000 0000作用在变量c的低位第一个字节上。

如果用一张图去形象地描述就如下:



其中一个方格(不是很方……)代表一个字节,红色的部分是a占用的内存空间,绿色是b,蓝色是c,黄色是d,而带斜线的部分则是被0x80000000赋值的内存区域。

好了,现在给出另外一个问题,如果struct是这样的:

[cpp] view
plaincopy

struct node {

int a;

char b;

char d;

int c;

};

那么问sizeof(node) = ?

给个提示,现在的话,内存占用示意图会变成这样:



那么结果各位应该也知道了。好了,一些拓展问题,上面这个struct的内存是在栈中分配的,如果是用new运算符,使其在堆上面分配,那么结果是什么?各位可以亲手试一下,直接把答案全部说出来,就有点没有意思了,哈哈


好啦,其实到这里,文章还没完,要再进一步讲一些

下面要讨论类,即class。

按照惯例,继续先上代码,请猜猜下面各个类其sizeof的返回值是什么:

[cpp] view
plaincopy

#include<iostream>

#include<stdio.h>

using namespace std;

class TwoIntNoVirtual {

int a;

int b;

void function() {

cout<<"Hello"<<endl;

}

};

class TwoIntOneVirtual {

int a;

int b;

virtual void function() {

}

};

class TwoIntTwoVirtual {

int a;

int b;

virtual void function() {

}

virtual void function2() {

}

};

class TwoIntOneVirtualDerived : public TwoIntOneVirtual {

void function2() {

}

};

class TwoIntTwoVirtualDerived : public TwoIntTwoVirtual {

void function() {

}

void function2() {

}

};

class OtherTest {

char e,f,g,h,i,j,k;

int a;

int b;

int c;

long long d;

virtual void function() {}

virtual void function2() {}

};

int main()

{

cout<<"TwoIntNoVirtual: "<<sizeof(TwoIntNoVirtual)<<endl;

cout<<"TwoIntOneVirtual: "<<sizeof(TwoIntOneVirtual)<<endl;

cout<<"TwoIntTwoVirtual: "<<sizeof(TwoIntTwoVirtual)<<endl;

cout<<"TwoIntOneVirtualDerived: "<<sizeof(TwoIntOneVirtualDerived)<<endl;

cout<<"TwoIntTwoVirtualDerived: "<<sizeof(TwoIntTwoVirtualDerived)<<endl;

cout<<"OtherTest: "<<sizeof(OtherTest)<<endl;

cout<<"Long long: "<<sizeof(long long)<<endl;

int stop;

cin>>stop;

}

其实从上面讲完struct,现在就是多加了两点:1 函数对size有什么影响? 2 虚函数对size有什么影响?

首先,对于32位的机器,指针的size是4个字节,其次虚函数有虚表的概念,神马是虚表?可以google一下,学C++一定要知道的基础知识啊。

这个程序的输出如下:

TwoIntNoVirtual: 8

TwoIntOneVirtual: 12

TwoIntTwoVirtual: 12

TwoIntOneVirtualDerived: 12

TwoIntTwoVirtualDerived: 12

OtherTest: 32

Long long: 8

成员变量对内存的占用和struct一样,普通的函数不会影响size,但是虚函数会使得size至少增加4,因为指向虚表的指针占用4个字节,然而要注意,1个虚函数或者n个虚函数对这个类取size是得到相同的结果(为什么?请参见拓展阅读1)。更多实验结果,各位要亲自动手试一试,最后再增加一个结论,注意以下两个struct(得到的size是相同的,都为16,感谢一楼指出原本的错误!):

[cpp] view
plaincopy

struct node1 {

int a;

char b, c, d, e;

long long f;

};

struct node2 {

int a;

int b;

long long c;

};

但是,下面这个struct,得到的size和上面是不同的(结果为24):

[cpp] view
plaincopy

class node0 {

int a;

char b;

short c;

char d;

long long f;

};

虽然a,b,c,d加起来是8个字节的宽度,但是其中d会被拥到“下一行”,可以用以下这段程序检验(同时发现了你可以在类的外部访问这个类的private成员了,神奇的指针啊):

[cpp] view
plaincopy

#include<iostream>

using namespace std;

class node0 {

int a;

char b;

short c;

char d;

long long f;

public:

node0(): a(1), b(5), c(10), d(15), f(20) {}

};

struct node1 {

int a;

char b;

char c;

char d;

char e;

long long f;

};

struct node2 {

int a;

int b;

long long c;

};

int main()

{

cout<<sizeof(node0)<<endl;

cout<<sizeof(node1)<<endl;

cout<<sizeof(node2)<<endl;

node0 n0;

void *p = &n0;

p = ((long long*)p) + 1;

cout<<(int)(*((char*)p))<<endl;

int stop;

cin>>stop;

}

输出结果是:

24

16

16

15

到底对齐要怎么对?经过查百度百科后(搜索“内存对齐”),发现其中有一条:结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节

这篇文章很多都是个人通过实验的猜想,各位务必亲测一下或者做进一步的研究,免得误导大家那就遗臭万年了!如果纰漏,欢迎指出,多谢!

相信上面的内容,对于我来说,应该能应付大部分sizeof题目,后来看了一些资料,发现有牛人已经对这个问题作过文,这里附上拓展阅读的链接:

拓展阅读1:C++虚函数表解释

拓展阅读2:C++对象的内存布局(上)

拓展阅读3:C++对象的内存布局(下)

******<转载说明>******

转载注明:诚实的偷包贼

原文地址:/article/1805739.html

******<转载说明/>******
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: