[C++]字节对齐与结构体大小
(下面部分内容转载自 内存对齐与补齐)
一、解释
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
二、基本概念
字节对齐:计算机存储系统中以Byte为单位存储数据,不同数据类型所占的空间不同,如:整型(int)数据占4个字节,字符型(char)数据占一个字节,短整型(short)数据占两个字节,等等。计算机为了快速的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字符型数据(char)可以存放在任何地址位置(被1整除),短整型(short)数据存储在地址能被2整除的起始位置。这就是默认字节对齐方式。
三、结构体长度求法
其实字节对齐的细节和具体编译器实现相关,但一般而言,满足以下几个准则:
1. struct中不包含其他struct或者union时:
1)对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员的当前偏移量必须是它自身数据类型的整数倍
2)结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
3)如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型(对所有的都满足)
2. struct中含有其他struct时(为了解释的方便,我们假设struct A中包含了struct B):
1) 在B之前的所有数据成员按照1-1)的规则进行对齐,计算其字节数
2) B的偏移量要保证为其最大数据类型的整数倍
3) 将B从A中拿出来,计算当B为一个单独的struct时,它所占用的字节数
4) B之后的数据成员仍旧按照1-1)的规则进行对齐(B之后的成员的偏移量为1)+3),如有需要,编译器会补齐),计算其字节数
5) 1)+3)+4)即为结构体长度(若此时该结果不满足1-2),则编译器还会以1-2)的方式对结构体本身进行一次内存对齐)
3. struct中含有union时
与2的步骤完全一致,只不过计算union的方法不一样(见七。)
四、空结构体
struct S5 { };
sizeof( S5 ); // 结果为1
“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。
五、有static的结构体
struct S4{
char a;
long b;
static long c; //静态
};
静态变量存放在全局数据区内,而sizeof计算栈中分配的空间的大小,故不计算在内,S4的大小为4+4=8。
六、举例说明
1.举例1
很显然默认对齐方式会浪费很多空间,例如如下结构:
struct student
{
char name[5];
int num;
short score;
}
本来只用了11bytes(5+4+2)的空间,但是由于int型默认4字节对齐,存放在地址能被4整除的起始位置,即:如果name[5]从0开始存放,它占5bytes,而num则从第8(偏移量)个字节开始存放。所以sizeof(student)=16。于是中间空出几个字节闲置着。但这样便于计算机快速读写数据,是一种以空间换取时间的方式。其数据对齐如下图:
|char|char|char|char|
|char|----|----|----|
|--------int--------|
|--short--|----|----|
如果我们将结构体中变量的顺序改变为:
struct student
{
int num;
char name[5];
short score;
}
则,num从0开始存放,而name从第4(偏移量)个字节开始存放,连续5个字节,score从第10(偏移量)开始存放,故sizeof(student)=12。其数据对齐如下图:
|--------int--------|
|char|char|char|char|
|char|----|--short--|
如果我们将结构体中变量的顺序再次改为为:
struct student
{
int num;
short score;
char name[5];
}
则,sizeof(student)=12。其数据对齐如下图:
|--------int--------|
|--short--|char|char|
|char|char|char|----|
2.举例2
(1)
struct test1
{ int a;
int b[4];
};
sizeof(test1)=sizeof(int)+4*sizeof(int)=4+4*4=20;
(2)
struct test2
{ char a;
int b;
double c;
bool d;
};
分析:该结构体最大长度double型,长度是8,因此结构体长度分两部分:
第一部分是a、 b、 c的长度和,长度分别为1,4,8,则该部分长度和为13,取8的大于13的最小倍数为16;
第二部分为d,长度为1,取大于1的8的最小倍数为8,
两部分和为24,故sizeof(test2)=24;
(3)
struct test3
{
char a;
test2 bb;//见上题
int cc;
}
分析:该结构体有三个成员,其中第二个bb是类型为test2的结构体,长度为24,且该结构体最大长度成员类型为double型,以后成员中没有double型,所以按bb分界为两部分:
第一部分有a 、bb两部分,a长度为1,bb长度为24,取8的大于25的最小倍数32;
第二部分有cc,长度为4,去8的大于4的最小倍数为8;
两部分之和为40,故sizeof(test3)=40;
(4)
struct test4
{
char a;
int b;
};
struct test5
{ char c;
test4 d;
double e;
bool f;
};
求sizeof(test5)
分析:test5明显含有结构体test4,按例2容易知道sizeof(test4)=8,且其成员最大长度为4;则结构体test5的最大成员长度为8(double 型),考试.大提示e是分界点,分test5为两部分:
第一部分由c 、d、e组成,长度为1、8、8,故和为17,取8的大于17的最小倍数为24;
第二部分由f组成,长度为1,取8的大于1的最小倍数为8,
两部分和为32,故sizeof(test5)=24+8=32;
七、union
union的长度取决于其中的长度最大的那个成员变量的长度。即union中成员变量是重叠摆放的,其开始地址相同。
其实union(共用体)的各个成员是以同一个地址开始存放的,每一个时刻只可以存储一个成员,这样就要求它在分配内存单元时候要满足两点:
1.一般而言,共用体类型实际占用存储空间为其最长的成员所占的存储空间;
2.若是该最长的存储空间对其他成员的元类型(如果是数组,取其类型的数据长度,例int a[5]为4)不满足整除关系,该最大空间自动延伸;
我们来看看这段代码:
union mm{
char a;//元长度1
int b[5];//元长度4
double c;//元长度8
int d[3];
};
本来mm的空间应该是sizeof(int)*5=20;但是如果只是20个单元的话,那可以存几个double型(8位)呢?两个半?当然不可以,所以mm的空间延伸为既要大于20,又要满足其他成员所需空间的整数倍,即24
所以union的存储空间先看它的成员中哪个占的空间最大,拿他与其他成员的元长度比较,如果可以整除就行。
八、指定对界
#pragma pack()命令
如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.
一般地,可以通过下面的方法来改变缺省的对界条件:
使用伪指令#pragma pack (n),编译器将按照n个字节对齐;
使用伪指令#pragma pack (),取消自定义字节对齐方式。
注意:如果#pragma pack (n)中指定的n大于结构体中最大成员size,则其不起作用,结构体仍然按照size最大的成员进行对界。
为了节省空间,我们可以在编码时通过#pragma pack()命令指定程序的对齐方式,括号中是对齐的字节数,若该命令括号中的内容为空,则为默认对齐方式。例如,对于上面第一个结构体,如果通过该命令手动设置对齐字节数如下:
#pragma pack(2) //设置2字节对齐
struct strdent
{
char name[5]; //本身1字节对齐,比2字节对齐小,按1字节对齐
int num; //本身4字节对齐,比2字节对齐大,按2字节对齐
short score; //本身也2字节对齐,仍然按2字节对齐
}
#pragma pack() // 恢复先前的pack设置,取消设置的字节对齐方式
则,num从第6(偏移量)个字节开始存放,score从第10(偏移量)个字节开始存放,故sizeof(student)=12,其数据对齐如下图:
|char|char|
|char|char|
|char|----|
|----int--|
|----int--|
|--short--|
这样改变默认的字节对齐方式可以更充分地利用存储空间,但是这会降低计算机读写数据的速度,是一种以时间换取空间的方式。
九、代码验证
- 代码
//------------------------------------
// 环境:VS2005
// 时间:2010.9.24
// 用途:结构体大小测试
// 作者:pppboy.blog.163.com
//-----------------------------------
#include "stdafx.h"
#include <iostream>
using namespace std;
//空
struct S0{ };
struct S1{
char a;
long b;
};
struct S2{
long b;
char a;
};
struct S3 {
char c;
struct S1 d;//结构体
long e;
};
struct S4{
char a;
long b;
static long c; //静态
};
struct S5{
char a;
long b;
char name[5]; //数组
};
//含有一个数组
struct S6{
char a;
long b;
int name[5]; //数组
};
struct student0
{
char name[5];
int num;
short score;
};
struct student1
{
int num;
char name[5];
short score;
};
struct student2
{
int num;
short score;
char name[5];
};
union union1
{
long a;
double b;
char name[9];
};
union union2{
char a;
int b[5];
double c;
int d[3];
};
int main(int argc, char* argv[])
{
cout << "char: " << sizeof(char) << endl; //1
cout << "long: " << sizeof(long) << endl; //4
cout << "int: " << sizeof(int) << endl; //4
cout << "S0: " << sizeof(S0) << endl; //1
cout << "S1: " << sizeof(S1) << endl; //8
cout << "S2: " << sizeof(S2) << endl; //8
cout << "S3: " << sizeof(S3) << endl; //24
cout << "S4: " << sizeof(S4) << endl; //8
cout << "S5: " << sizeof(S5) << endl; //16
cout << "S6: " << sizeof(S6) << endl; //28
cout << "union1 :" << sizeof(union1) << endl;
cout << "union2 :" << sizeof(union2) << endl;
cout << "student0: " << sizeof(student0) << endl;
cout << "student1: " << sizeof(student1) << endl;
cout << "student2: " << sizeof(student2) << endl;
system("pause");
return 0;
}
- 输出
//这是默认的结果(8字节对齐)
char: 1
long: 4
int: 4
S0: 1
S1: 8
S2: 8
S3: 16
S4: 8
S5: 16
S6: 28
union1 :16
union2 :24
student0: 16
student1: 12
student2: 12
请按任意键继续. . .
//这是16字节对齐的结果,可以看到当设置16字节对齐时,确实没什么效果,里面最大的是double,也就是8字节,#pragma pack (n)中指定的n大于结构体中最大成员size,则其不起作用。
char: 1
long: 4
int: 4
double:8
S0: 1
S1: 8
S2: 8
S3: 16
S4: 8
S5: 16
S6: 28
union1 :16
union2 :24
student0: 16
student1: 12
student2: 12
请按任意键继续. . .
//这是2字节对齐的结果,可以慢慢参考研究
char: 1
long: 4
int: 4
double:8
S0: 1
S1: 6
S2: 6
S3: 12
S4: 6
S5: 12
S6: 26
union1 :10
union2 :20
student0: 12
student1: 12
student2: 12
请按任意键继续. . .