您的位置:首页 > 编程语言 > C语言/C++

学习笔记-C/C++-结构体与sizeof,内存对齐的题目怎么做

2015-03-22 17:19 489 查看
如有错误请跟帖指出!
本文赘述较多,但也是为了给不会的同学准确全面讲解。

基础篇

字节对齐的原因

一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.
可以提高CPU存储效率。


影响结构体的sizeof的因素:

1)  不同的系统(如32位或16位系统):不同的系统下int等类型的长度是变化  的,如对于16位系统,int的长度(字节)为2,而在32位系统下,int的长度为4(现在通常是默认32位的情况);因此如果结构体中有int等类型的成员,在不同的系统中得到的sizeof值是不相同的。
2)   编译器设置中的对齐方式:对齐方式的作用常常会让我们对结构体的sizeof值感到惊讶。

 

不同变量的存储大小

    
Type

存储所占字节

对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

Alignment

和左边一列一个意思
char
1
偏移量必须为sizeof(char)即1的倍数
在字节边界上对齐
short (16-bit)
2
偏移量必须为sizeof(short)即2的倍数
在双字节边界上对齐
int and long (32-bit)

指针(unsigned long int)
4 在32位系统下int 和long是一样的?
偏移量必须为sizeof(int)即4的倍数
在4字节边界上对齐
枚举类型的默认类型是int型
4
  
float
4
偏移量必须为sizeof(float)即4的倍数
在4字节边界上对齐
double

long long
8
偏移量必须为sizeof(double)即8的倍数
在8字节边界上对齐
structure
  单独考虑结构体的个成员,它们在不同的字节边界上对齐。

其中最大的字节边界数就是该结构的字节边界数。
 静态变量
0
 MSDN原话:Largest alignment requirement of any member
空结构体
1 必须保证结构体的每一个实例在内存中都有独一无二的地址
  

#pragma pack().

 
typedef struct bb

{

int id; //[0]....[3]

double weight; //[8].....[15]      原则1

float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3

}BB; //24


 
typedef struct aa

{

char name[2]; //[0],[1]

int id; //[4]...[7]          原则1

double score; //[8]....[15]    

short grade; //[16],[17]        

BB b; //[24]......[47]          原则2

}AA; //48

 
再讲讲#pragma pack().

在代码前加一句#pragma pack(1),你会很高兴的发现

bb是4+8+4=16,aa是2+4+8+2+16=32;

这不是理想中的没有内存对齐的世界吗.没错,#pragma pack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则.

明白了不?

#pragma pack (n)这个语句用于设置结构体的内存对齐方式,具体作用下面再说。在linux gcc下n可取的值为:1,2,4,当n大于4时按4处理。如果程序中没用显试写出这个语句,那么在linux gcc下,它会对所有结构体都采用#pragma pack (4)的内存对齐方式。需要注意的是,在不同的编译平台上默认的内存对齐方式是不同的。如在VC中,默认是以#pragma pack (8)的方式进行对齐。

 

对齐规则1 - 数据成员对齐规则 操作系统的默认对齐系数

 


1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。

 
对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4整除。变量的对齐规则如下(32位系统):
Type

Alignment

char
在字节边界上对齐
short (16-bit)
在双字节边界上对齐
int and long (32-bit)
在4字节边界上对齐
float
在4字节边界上对齐
double
在8字节边界上对齐
structures
单独考虑结构体的个成员,它们在不同的字节边界上对齐。

其中最大的字节边界数就是该结构的字节边界数。
  
MSDN原话:Largest alignment requirement of any member
如果结构体中有结构体成员,那么这是一个递归的过程。

最大字节边界数 即操作系统的默认对齐系数

最大对齐字节数可由编译器设定
设编译器设定的最大对齐字节边界数为n,对于结构体中的某一成员item,它相对于结构首地址的实际字节对齐数目X应该满足以下规则:
X  =  min(n, sizeof(item))
例如,对于结构体 struct {char a;  int  b} T;
当位于32位系统,n=8时:
a的偏移为0,
b的偏移为4,中间填充了3个字节, b的X为4;
 
 当位于32位系统,n=2时:
a的偏移为0,
b的偏移为2,中间填充了1个字节,b的X为2;
 
 n一般不会显示给出,每个操作系统都自己的默认内存对齐系数,如果是新版本的操作系统,默认对齐系数一般都是8,因为操作系统定义的最大类型储存单元就是8个字节,如long long 或double。

对齐规则2-:结构体作为成员

 
如果结构体中有结构体成员,那么这是一个递归的过程。 雅美蝶,这句话很难理解啊。

 
2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)

注char【8】看做 8个元素,而不是一个元素


 

 

对齐规则3-收尾工作

3:收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐. 这个说法是错的
正确的说法是在数据成员完成自身的对齐后,结构体本身也要进行对齐。意思是该结构体的大小必须是结构体的对齐模数的整数倍。如果其大小不是,那么则在最后一个成员的后面填充字节。
结构体成员的地址必须安排在成员大小的整数倍上或者是#pragma pack(n)所指定的n的倍数上;取两者的最小值,即MIN(sizeof(mem), n),称MIN(sizeof(mem), n)为该结构体的成员对齐模数。同时该结构体的总大小必须为MIN(n, MAX(sizeof(mem1), siezof(mem2)…))的整数倍;而称MIN(n, MAX(sizeof(mem1), siezof(mem2)…))为该结构体的对齐模数。

n是系统的对齐模数例如:32位系统,n=8,
结构体 struct {char a; char b;} T;
 struct {char a;  int  b;} T1;
 struct {char a;  int  b; char  c;} T2;
 
 sizeof(T) == 2;      N = 1   没有填充
sizeof(T1) == 8;      N = 4   中间填充了3字节
sizeof(T2)==12;    N = 4   中间,结尾各填充了3字节
 
 

注意:

 
1)  对于空结构体,sizeof == 1;因为必须保证结构体的每一个实例在内存中都有独一无二的地址。
2)  结构体的静态成员不对结构体的大小产生影响,因为静态变量的存储位置 C++内存储存篇
与结构体的实例地址无关。例如:
struct {static int I;} T;      struct {char a; static int I;} T1;
sizeof(T) == 1;            sizeof(T1) == 1;
     3) 某些编译器支持扩展指令设置变量或结构的对齐方式,如VC,

 

 

 

习题演练

***表示实际存储,空白字节对齐, 绿色表示尾部根据最大变量类型补足。

1、

struct MyStruct

{

double dda1;

char dda;

int type

};

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16

存储示意图
                
 
2、

struct MyStruct
{
char dda; //偏移量为0,满足对齐方式,dda占用1个字节;

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8
//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

//方式),因此VC自动填充7个字节,dda1存放在偏移量为8
//的地址上,它占用8个字节。

int type; //下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

//数,满足int的对齐方式,所以不需要VC自动填充,type存

//放在偏移量为16的地址上,它占用4个字节。

};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

//sizeof(double)=8的倍数。

所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
                        
 
3

typedef struct bb

{

int id; //[0]....[3]

double weight; //[8].....[15]      原则1

float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3

}BB; //24


 
typedef struct aa

{

char name[2]; //[0],[1]

int id; //[4]...[7]          原则1

double score; //[8]....[15]    

short grade; //[16],[17]        

BB b; //[24]......[47]          原则2

}AA; //48

 
typedef struct cc

{

char name[2]; //[0],[1]

BB b; //[8]......[31]          原则2

int id; //[32]...[35]          原则1

double score; //[40]....[47]    

short grade; //[48],[55] 原则3        

}CC; //56

 

 
int main()

{

AA a;

cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;

return 0;

}

结果是:

48 24

ok,上面的全看明白了,内存对齐基本过关.

 

 

拓展篇

 
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。下面举例说明其用法。

#pragma pack(push) //保存对齐状态

#pragma pack(4)//设定为4字节对齐

struct test

{

char m1;

double m4;

int m3;

};

#pragma pack(pop)//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

 
再看下面这个例子

 
#pragma pack(8)

struct S1{

char a;

long b;

};

struct S2 {

char c;

struct S1 d;

long long e;

};

#pragma pack()

sizeof(S2)结果为24.

成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.

也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.

S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;

S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.

a b

S1的内存布局:11**,1111,

c S1.a S1.b d

S2的内存布局:1***,11**,1111,****11111111

 
这里有三点很重要:

1.每个成员分别按自己的方式对齐,并能最小化长度。

2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

 
Win32平台下的微软 编译器(cl.exe for 80×86)的对齐策略:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

 

后记

如果还有不懂的,可以在下面这些提高篇文章里寻找答案 嘻嘻

这些文章我也没有细度过

http://www.cnblogs.com/wwping/articles/2314080.html     

http://blog.csdn.net/hbuxiaofei/article/details/9491953

http://wenku.baidu.com/link?url=YD_r5dPoVd5JxNoT096l9H-HTs4vJdDZ2AuPgd4cgn17yf3tuZLC_yI7N2lYlD529uFiRx3c_asgLbt-tft-YNi_KIMry7LyRnu3QnRnPWy

http://wenku.baidu.com/link?url=y-8Ad0Z-e1enhDgkxS_axxfVbxUyJVOWAnNjG-21VD93af1TlGmq_i5i13MhCUVOTJKK67geWFu4vlf97XrzPXqrzRNOo5AihUQ0wkD_q6W
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: