最近面试多,出的题目很多都有如下形式,给定一个class或者struct的定义,例如这样:
struct node { int a; char b; int c; char d; };
问题是:sizeof(node) = ?
之前了解过对齐的概念,但是不深入,所以在这里自己做了一些小测试,说一下自己的看法。先告诉大家吧,上面那题答案是16。
如果用“对齐”的说法,那么就是先找到这个struct中占内存最大的一个类型——“int”,然后简单地进行“变量个数 * sizeof(int) = 4 * 4 = 16”即可。但是这样的做法其实有点问题,下面会继续探讨。
先上一段代码(可以自己先想一下这段代码的输出是什么?):
#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; }
代码输出:
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是这样的:
struct node { int a; char b; char d; int c; };
那么问sizeof(node) = ?
给个提示,现在的话,内存占用示意图会变成这样:
那么结果各位应该也知道了。好了,一些拓展问题,上面这个struct的内存是在栈中分配的,如果是用new运算符,使其在堆上面分配,那么结果是什么?各位可以亲手试一下,直接把答案全部说出来,就有点没有意思了,哈哈
好啦,其实到这里,文章还没完,要再进一步讲一些
下面要讨论类,即class。
按照惯例,继续先上代码,请猜猜下面各个类其sizeof的返回值是什么:
#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,感谢旧博客中一楼指出原本的错误!):
struct node1 { int a; char b, c, d, e; long long f; }; struct node2 { int a; int b; long long c; };
但是,下面这个struct,得到的size和上面是不同的(结果为24):
class node0 { int a; char b; short c; char d; long long f; };
虽然a,b,c,d加起来是8个字节的宽度,但是其中d会被拥到“下一行”,可以用以下这段程序检验(同时发现了你可以在类的外部访问这个类的private成员了,神奇的指针啊):
#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++对象的内存布局(下)
- 如何找相交单链表的第一个交点
- 路由器连接校园网并发WIFI:WR703N路由器安装OpenWRT并运行H3C客户端操作步骤(主要针对中山大学东校区)
回复测试
你的博客很好,赞一个
多谢~: )