sizeof 的使用
sizeof是一个操作符,其作用是返回一个对象或类型所占的内存字节数;对象可以是各种类型的变量以及表达式。
- 运算符,非函数;
- 编译期求值,所以 sizeof 的结果是常量;
- 求的是类型的长度,表达式是不需要求值的!
#define sizeof(type) ((size_t) ((type*)0 + 1)) //实现的原理就是利用指针的步进值
???
此处理解: sizeof(++i) 中 i 值为什么没有 +1
???
1. 基本数据类型的使用
对于内置数据类型(如:short,int,long,double,float等),它们的内存大小是和系统相关的,为了程序的跨平台使用,一般 通过sizeof来获取基本数据类型占用的内存大小。
2. 结构体的使用
结构体字节对齐的细节和编译器有关,但是一般而言满足一下准则:
- 结构体变量的首地址能够被其最宽基本数据类型的大小整除;
- 结构体的每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间家上填充字节;
- 结构体的总大小为结构体最宽基本数据类型成员大小的整数倍,如有需要,编译器会在最末一个成员后加上填充字节。
struct S1
{
char a;
int b;
};
struct S2
{
int b;
char a;
};
struct S3
{
};
int main()
{
sizeof(S1); //值为8,字节对齐,在char之后会填充3个字节。
sizeof(S2); //值为8,字节对齐,在char之后会填充3个字节。
sizeof(S3); //值为1,空结构体也占内存
return 0;
}
字节对齐
许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器总是从存储器中取出8个字节,则地址必须为8的倍数。如果我们能保证将所有的double类型数据的地址对齐成8的倍数,那么就可以用一个存储器操作来读或者写值了。否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中。另外,假设一个整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。
不同编译器计算法不同
struct node{
double a;
int b;
int c;
char d;
}同样X86架构上:vc编译后sizeof(node)=24;linux gcc 编译后 sizeof(node)=20;因为:在VC中规定, 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;而在gcc中规定对齐模数最大只能是4,也就是说,即使结构体中有double类型,对齐模数还是4,所以数据是按照1,2,4对齐的。所以:
vc下 :sizeof(node)=a(8)+b(4)+c(4)+{d(1)+7} = 24;
linux gcc下: sizeof(node)= a(4)+a(4)+b(4)+c(4)+{d(1)+3} = 20;
3. 联合体的使用
联合体在内存组织上是重叠式的,各成员共享一段内存,所以整个联合体的sizeof也就是所有成员中sizeof的最大值。
union test
{
int a;
float b;
double c;
char d;
};
sizeof(test); //8
4. 数组的使用
数组的sizeof值等于数组所占用的内存字节数。
void func(char a[10])
{
int size = sizeof(a); //4
}
int main()
{
char a[10];
char str[] = "helloworld";
cout << sizeof(a) << endl; //10
cout << sizeof(str) << endl; //11,包含结尾符
}
当字符数组表示字符串时,其sizeof值将包含结尾符‘/0’;
当数组为形参时,其sizeof值相当于指针的sizeof。
5. 指针的使用
指针是用来记录另一个对象的地址,所以指针的内存大小当然就等于计算机内部地址总线的宽度;在32位计算机中,一个指针变量的返回值必定是4。
char *b = "helloworld";
char *c[10];
double *d;
int **e;
void (*pf)();
cout<<"sizeof(b)="<<sizeof(b) << endl; //指针指向字符串,值为4
cout<<"sizeof(*b)="<<sizeof(*b) << endl; //指针指向字符,值为1
cout<<"sizeof(d)="<<sizeof(d) <<endl; //指针,值为4
cout<<"sizeof(*d)="<<sizeof(*d) <<endl; //指针指向浮点数,值为8
cout<<"sizeof(e)"<<sizeof(e)<<endl; //指针指向指针,值为4
cout<<"sizeof(c)"<<sizeof(c)<<endl; //指针数组,值为40
cout<<"sizeof(pf)="<<sizeof(pf)<<endl; //函数指针,值为4
6. 函数的使用
sizeof也可对一个函数调用求值,其结果是函数返回值类型的大小。
- 不可以对返回值类型为空的函数求值。
- 不可以对函数名求值。
- 对有参数的函数,在用sizeof时,须写上实参表。
7. 类的使用
空类的大小为1字节
class A {};
cout << "sizeof(A)= " << sizeof(A) << endl; //空类的大小为1
成员函数、静态数据成员都是不占用类对象的存储空间。
class B {
public:
int i;
};
class B1 {
public:
static int j;
static int i;
static int k;
};
class B2 {
public:
void add() {}
void sub() {}
};
cout << "sizeof(B)= " << sizeof(B) << endl; //4
cout << "sizeof(B1)= " << sizeof(B1) << endl; //1 : 静态成员变量不占用类的大小
cout << "sizeof(B2)= " << sizeof(B2) << endl; //1 :成员函数不占用类的大小
包含虚函数的类,会存在一个虚指针
class B2 {
public:
void add() {}
void sub() {}
};
class C {
public:
virtual void add() {}
};
class C1 {
public:
virtual void add() {}
virtual void sub() {}
};
cout << "sizeof(B2)= " << sizeof(B2) << endl; //1 :成员函数不占用类的大小
cout << "sizeof(C)= " << sizeof(C) << endl; //4 : 虚指针的大小
cout << "sizeof(C1)= " << sizeof(C1) << endl; //4 :只有一个虚指针
普通继承时派生类继承了所有基类的函数与成员
class D {
public:
char c;
int k;
};
class D2 : public D {
public:
short a;
long b;
};
cout << "sizeof(D)= " << sizeof(D) << endl;// 8 :字节对齐
cout << "sizeof(D2)= " << sizeof(D2) << endl;// 16: 包含基类的数据大小
包含虚函数类被继承时会继承其虚指针
class E {
public:
virtual void add() {}
};
class E1 : public E{
public:
};
cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(E1)= " << sizeof(E1) << endl;//4:继承虚指针
虚继承时子类会继承基类的vptr。
class E {
public:
virtual void add() {}
};
class F {
public:
virtual void sub() {}
};
class F1 : virtual public E, virtual public F {
public:
virtual void func() {}
};
cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(F)= " << sizeof(F) << endl;//4
/*
因为是虚继承,因此引入一个指针指向虚继承的基类,第二由于在基类中有虚函数,因此需要指针指向其虚函数表,由于派生类自己本身也有自己的虚函数
*/
cout << "sizeof(F1)= " << sizeof(F1) << endl;//16
不同编译器下的虚继承
1、对虚继承层次的对象的内存布局,在不同编译器实现有所区别。
首先,说说GCC的编译器.
它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。
class A { int a; virtual ~A(){} }; class B:virtual public A{ virtual void myfunB(){} }; class C:virtual public A{ virtual void myfunC(){} }; class D:public B,public C{ virtual void myfunD(){} };
以上代码中sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.
解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。
如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16.注意此时虽然D的大小和虚继承一样,但是内存布局却不同。
然后,来看看VC的编译器
vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。
代码同上。
运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.
解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。
同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。
测试代码:
#include <iostream>
using namespace std;
class A {};
class B {
public:
int i;
};
class B1 {
public:
static int j;
static int i;
static int k;
};
class B2 {
public:
void add() {}
void sub() {}
};
class C {
public:
virtual void add() {}
};
class C1 {
public:
virtual void add() {}
virtual void sub() {}
};
class D {
public:
char c;
int k;
};
class D1 {
public:
short a;
long b;
};
class D3 {
public:
D d;
char c;
};
class D2 : public D {
public:
short a;
long b;
};
class E {
public:
virtual void add() {}
};
class E1 : public E{
public:
};
class F {
public:
virtual void sub() {}
};
class F1 : virtual public E, virtual public F {
public:
virtual void func() {}
};
struct node {
double a;
int b;
int c;
char d;
};
int main()
{
char *b = "helloworld";
char *c[10];
double *d;
int **e;
void(*pf)();
cout << "sizeof(node)=" << sizeof(node) << endl;//24: vc x86
cout << "sizeof(b)=" << sizeof(b) << endl; //指针指向字符串,值为4
cout << "sizeof(*b)=" << sizeof(*b) << endl; //指针指向字符,值为1
cout << "sizeof(d)=" << sizeof(d) << endl; //指针,值为4
cout << "sizeof(*d)=" << sizeof(*d) << endl; //指针指向浮点数,值为8
cout << "sizeof(e)" << sizeof(e) << endl; //指针指向指针,值为4
cout << "sizeof(c)" << sizeof(c) << endl; //指针数组,值为40
cout << "sizeof(pf)=" << sizeof(pf) << endl; //函数指针,值为4
//测试平台x86
cout << "sizeof(A)= " << sizeof(A) << endl; //空类的大小为1
cout << "sizeof(B)= " << sizeof(B) << endl;//4
cout << "sizeof(B1)= " << sizeof(B1) << endl;//1 : 静态成员变量不占用类的大小
cout << "sizeof(B2)= " << sizeof(B2) << endl;//1 :成员函数不占用类的大小
cout << "sizeof(C)= " << sizeof(C) << endl;//4 : 虚指针的大小
cout << "sizeof(C1)= " << sizeof(C1) << endl;// 4:只有一个虚指针
cout << "sizeof(D)= " << sizeof(D) << endl;// 8 :字节对齐
cout << "sizeof(D1)= " << sizeof(D1) << endl;// 8: long 占4个字节
cout << "sizeof(D2)= " << sizeof(D2) << endl;// 16: 包含基类的数据大小
cout << "sizeof(D3)= " << sizeof(D3) << endl;//12
cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(E1)= " << sizeof(E1) << endl;//4:继承虚指针
cout << "sizeof(E)= " << sizeof(E) << endl;//4
cout << "sizeof(F)= " << sizeof(F) << endl;//4
cout << "sizeof(F1)= " << sizeof(F1) << endl;//16
return 0;
}
输出结果(visual studio x86):