C++内存对齐

内存对齐规律

  • 各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。

  • 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。

  • 同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

1、 对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。

2、 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

#include <iostream>
using namespace std;

struct Test
{

int a; //4
char b;//1+1
short c;//2
char d;//1+3
};

struct Test1
{
int a; //4
char b;//1+1
short c;//2
char d;//1+6
double t;//8
};
struct X1
{
int i;//4个字节
char c1;//1个字节
char c2;//1个字节
};

struct X2
{
char c1;//1个字节
int i;//4个字节
char c2;//1个字节
};

struct X3
{
char c1;//1个字节
char c2;//1个字节
int i;//4个字节
};
int main()
{
cout << "long " << sizeof(long) << "\n";
cout << "float " << sizeof(float) << "\n";
cout << "int " << sizeof(int) << "\n";
cout << "char " << sizeof(char) << "\n";
cout << "double " << sizeof(double) << "\n";
X1 x1;
X2 x2;
X3 x3;
Test t;
Test1 t1;
cout << "x1 的大小 " << sizeof(x1) << "\n";
cout << "x2 的大小 " << sizeof(x2) << "\n";
cout << "x3 的大小 " << sizeof(x3) << "\n";
cout << "t 的大小 " << sizeof(t) << "\n";
cout << "t1 的大小 " << sizeof(t1) << "\n";
return 0;
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
long 4
float 4
int 4
char 1
double 8

x1 的大小 8
x2 的大小 12
x3 的大小 8
t 的大小 12
t1 的大小 24

设置内存对齐字节数

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma pack(4)  //sizeof(Test1)=28
#pragma pack(8) //sizeof(Test1)=32 VS 默认

struct Test1
{
char c;
short sh;
int a;
float f;
int *p;
char *s;
double d;
};

关于静态变量static

静态变量的存放位置与结构体实例的存储地址无关,是单独存放在静态数据区的,因此用siezof计算其大小时没有将静态成员所占的空间计算进来。

关于类

空类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。

(一)类内部的成员变量:

  • 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
  • static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

(二)类内部的成员函数:

  • 普通函数:不占用内存。
  • 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
1
2
3
//sizeof(cBase)=1
#pragma pack(4)
class cBase{};

不包含虚函数时,对于类中的成员变量按结构体对齐方式处理,普通函数函数不占内存。sizeof(CBase1)=8

1
2
3
4
5
6
7
8
9
10
#pragma pack(4)  
class CBase1
{
private:
char c;
short sh;
int a;
public:
void fOut(){ cout << "hello" << endl; }
};

包含虚函数时,类中需要保存虚函数表的入口地址指针,即需要多保存一个指针。这个值跟虚函数的个数多少没有关系。sizeof(CBase2)=12

1
2
3
4
5
6
7
8
9
10
#pragma pack(4)  
class CBase2
{
private:
char c;
short sh;
int a;
public:
virtual void fOut(){ cout << "hello" << endl; }
};

子类所占内存大小是父类+自身成员变量的值。特别注意的是,子类与父类共享同一个虚函数指针,因此当子类新声明一个虚函数时,不必在对其保存虚函数表指针入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma pack(4)  
class CBase2
{
private:
char c;
short sh;
int a;
public:
virtual void fOut(){ cout << "virtual 1" << endl; }
};
class cDerive :public CBase
{
private :
int n;
public:
virtual void fPut(){ cout << "virtual 2"; }
};