参考文章:
- https://www.nowcoder.com/issue/tutorial?tutorialId=93&uuid=8f38bec08f974de192275e5366d8ae24
- https://zhuanlan.zhihu.com/p/30007037
计算机系统对基本类型数据在内存中存放的位置有限制,
它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间,各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。
看下面程序,理论上char name[10]
占10byte,age
占4byte,weight
、height
各占8字节,那么将它们放到一个结构体中应该占30byte;但是实际上得到的结果是32byte,这就是内存对齐所导致的。
#include
using namespace std;
class People {
public:
char name[10];
int age;//4
double weight;//8
double height;//8
void say(string word){};
void run(){};
};
int main() {
People p;
cout << "size of string = " << sizeof(string) << endl;
cout << "size of People = " << sizeof(People) << " size of p = " << sizeof(p) << endl;//内存对齐
cout << "address of name = " << &p.name << endl;
cout << "address of age = " << &p.age << endl;
cout << "address of weight = " << &p.weight << endl;
cout << "address of height = " << &p.height << endl;
return 0;
}
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,这些存取单位称为内存存取粒度。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性即所谓的对齐,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。
假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作。
现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。
case1:利用#pragma pack(1)修改内存对齐方式为1byte:
#include
#pragma pack(1)//修改内存对齐的规则为1byte
using namespace std;
class People {
public:
char name[10];
int age;//4 -> 8
double weight;//8
double height;//8
void say(string word){};
void run(){};
};
int main() {
People p;
cout << "size of People = " << sizeof(People) << " size of p = " << sizeof(p) << endl;//内存对齐
cout << "address of name = " << &p.name << endl;
cout << "address of age = " << &p.age << endl;
cout << "address of weight = " << &p.weight << endl;
cout << "address of height = " << &p.height << endl;
return 0;
}
case2:内存对齐导致类成员占用内存大小与书写类成员属性先后有关:
#include
using namespace std;
class Test {
int a;
double c;
int b;
};
int main() {
cout << "size of Test = " << sizeof(Test) << endl;
return 0;
}
#include
using namespace std;
class Test {
int a;
int b;
double c;
};
int main() {
cout << "size of Test = " << sizeof(Test) << endl;
return 0;
}
case3:结构体的对齐值是其成员的最大对齐值:
#include
using namespace std;
class Test {
int a;
int b;
int c;
};
int main() {
cout << "size of Test = " << sizeof(Test) << endl;
return 0;
}
case4:类内存使用的特殊情况:如果一个类中没有定义任何内容,则编译器会自动在类中加入一个char类型使其占用1byte大小,
#include
using namespace std;
class Test {};
int main() {
cout << "size of Test = " << sizeof(Test) << endl;
return 0;
}
在客户端与服务端交互、进行网络通讯时,当这样一个类的结构被序列化出来之后,以上例为例,非内存对齐的情况下为32byte,
内存对齐应用与3种数据类型:struct、class、union
struct example {
int a[5];
char b;
double c;
} test_struct;
int result = sizeof(test_struct);
/* 如果我们不考虑字节对齐,那么内存地址0x0021不是double(8Byte)的整数倍,所以需要字节对齐,
那么此时满足是double(8Byte)的整数倍的最小整数是0x0024,说明此时char b对齐int扩充了三个字节。
所以最后的结果是result=32 */
union example {
int a[5];
char b;
double c;
};
int result = sizeof(example);
/* 如果以最长20字节为准,内部double占8字节,这段内存的地址0x00000020并不是double的整数倍,
只有当最小为0x00000024时可以满足整除double(8Byte)同时又可以容纳int a[5]的大小,
所以正确的结果应该是result=24 */
struct example {
char b;
double c;
int a;
} test_struct;
int result = sizeof(test_struct);
/* 字节对齐除了内存起始地址要是数据类型的整数倍以外,还要满足一个条件,
那就是占用的内存空间大小需要是结构体中占用最大内存空间的类型的整数倍,
所以20不是double(8Byte)的整数倍,我们还要扩充四个字节,
最后的结果是result=24 */