内存对齐

1.内存对齐

ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址

实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。

 

/*Win32平台下的微软C编译器(cl.exe for80x86)在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。

 

例如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。*/

 

内存对齐方式体现了内存存储空间存储效率的一种平衡。例如对于一次性存入double(8字节)类型,那么在存入其他类型的同时可能存在内存的浪费。而对于以4为对齐模数的情况下,一个double数据需要两个存储周期才能完成,降低了存储效率

好的内存对齐方式也能提高内存命中率。

 

 

2.结构的大小

 

更具体的可参见:http://blog.csdn.net/lbjfeng/archive/2009/03/04/3952387.aspx

 

可以 使用预编译指令#progma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。

#progma pack (2) /*指定按2字节对齐*/

struct C

{

char b;

int a;

short c;

};

#progma pack () /*取消指定对齐,恢复缺省对齐*/

sizeof(struct C)值是8。

 

 

 

如果一个程序中的两个模块是用不同的对齐选项分别编译的,那么它很可能会产生一些非常微妙的错误。如果你的程序确实有很难理解的行为,不防仔细检查一下各个模块的编译选项。

 

 

 

3.位结构

 

//具体的实现和应用还不知道只是做个简单的了解

 

位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。

 

位结构定义的一般形式为:

 

struct位结构名

 

{

 

       数据类型变量名: 整型常数;

 

      数据类型变量名: 整型常数;

 

} 位结构变量;

 

其中: 数据类型必须是int(unsigned或signed)。整型常数必须是非负的整数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。

 

 

 

struct mybitfields

{

unsigned short a : 4;

unsigned short b : 5;

unsigned short c : 7;

}test;

 

void main(void)

{

int i;

test.a=2;

test.b=3;

test.c=0;

i=*((short *)&test);

printf("%d ",i);

}

这里的冒号相当于分配几位空间,也即在定义结构体的时候,分配的成员a 4位的空间, b 5位,c 7位,一共是16位,正好两个字节。一个简单的示意:

 

变量名  位数

test   15 14 13 12 11 10 9 |8 7 6 5 4 |3 2 1 0

test.a                  |       |0 0 1 0

test.b                   |0 0 0 1 1 |

test.c  0  0  0  0  0 0 0 |              |

在执行i=*((short*)&test); 时,取从地址&test开始两个字节(short占两个字节)的内容转化为short型数据,即为0x0032,再

 

转为int型为0x00000032,即50。输出的结果就是50。

 

2 含位域结构体的sizeof:

前面已经说过,位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。

C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

使用位域的主要目的是压缩存储,其大致规则为:

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字

段将紧邻前一个字段存储,直到不能容纳为止;

2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字

段将从新的存储单元开始,其偏移量为其类型大小的整数倍

3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方

,Dev-C++采取压缩方式

4) 如果位域字段之间穿插着非位域字段,则不进行压缩

5) 整个结构体的总大小为最宽基本类型成员大小的整数倍

还是让我们来看看例子。

示例1:

struct BF1

{

   char f1 : 3;

   char f2 : 4;

   char f3 : 5;

};

其内存布局为:

 |__f1___|____f2___ |__|____f3______|______|

 |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|

位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。

示例2:

struct BF2

{

   char f1 : 3;

   short f2 : 4;

   char f3 : 5;

};

由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。

示例3:

struct BF3

{

   char f1 : 3;

   char f2;

   char f3 : 5;

};

非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。

 

 

 

写出下列程序在X86上的运行结果。

struct mybitfields

{

unsigned short a : 4;

unsigned short b : 5;

unsigned short c : 7;

}test;

 

void main(void)

{

int i;

test.a=2;

test.b=3;

test.c=0;

i=*((short *)&test);

printf("%d ",i);

}

 

这个题的为难之处呢,就在于前面定义结构体里面用到的冒号,如果你能理解这个符号的含义,那么问题就很好解决了。这里的冒号相当于分配几位空间,也即在定义结构体的时候,分配的成员a 4位的空间, b 5位,c 7位,一共是16位,正好两个字节。下面画一个简单的示意:

变量名 位数

test 15 14 13 12 11 10 9 |8 7 6 5 4 |3 2 10

test.a | |0 0 1 0

test.b |0 0 0 1 1 |

test.c 0 0 0 0 0 0 0 | |

在执行i=*((short*)&test); 时,取从地址&test开始两个字节(short占两个字节)的内容转化为short型数据,即为0x0032,再转为int型为0x00000032,即50。输出的结果就是50。当然,这里还涉及到字节及位的存储顺序问题,后面再说。

 

前面定义的结构体被称为位结构体。所谓位结构体,是一种特殊的结构体,在需要按位访问字节或字的一个或多个位时,位结构体比按位操作要更方便一些。

位结构体的定义方式如下:

struct [位结构体名]{

数据类型 变量名:整数常数;

...

}位结构变量;

说明:

1)这里的数据类型只能为int型(包括signed和unsigned)

2)整数常数必须为0~15之间的整数,当该常数为1时,数据类型为unsigned(显然嘛,只有一位,咋表示signed?光一符号?没意义呀);

3)按数据类型变量名:整数常数;方式定义的结构成员称为位结构成员,好像也叫位域,在一个位结构体中,可以同时包含位结构成员及普通的结构成员

4)位结构成员不能是指针或数据,但结构变量可以是指针或数据;

5)位结构体所占用的位数由各个位结构成员的位数总各决定。如在前面定义的结构体中,一共占用4+5+7=16位,两个字节。另外我们看到,在定义位结构成员时,必须指定数据类型,这个数据类型在位结构体占用多少内存时也起到不少的作用。举个例子:

struct mybitfieldA{

char a:4;

char b:3;

}testA;

 

struct mybitfieldB{

short a:4;

short b:3;

}testB;

这里,testA占用一个字节,而testB占用两个字节。知道原因了吧。在testA中,是以char来定义位域的,char是一个字节的,因此,位域占用的单位也按字节做单位,也即,如果不满一个字节的话按一个字节算(未定义的位按零处理)。而在testB中,short为两个字节,所以了,不满两个字节的都按两个字节算(未定义位按零处理)

 

关于位结构体在内存中的存储问题

Kevin's Theory #2: In a C structure thatcontains bit fields, if field A is defined in front of field B, then field A alwaysoccupies a lower bit address than field B. (来自http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxk&Number=638637&page=0&view=collapsed&sb=5&o=all&fpart=all)

说的是,在C结构体中,如果一个位域A在另一个位域B之前定义,那么位域A将存储在比B小的位地址中。

如果一个位域有多个位时,各个位的排列顺序通常是按CPU的端模式(Endianess)来进行的,即在大端模式(big endian)下,高有效位在低位地址,小端模式则相反。

补充说明一个关于位域与普通结构成员一起使用的问题

先看一个例子

struct mybitfield{

char a:4;

char b:3;

char aa;

char c:1;}test;

这种情况下,test应该占几个字节呢?2个(4+3+1=8占一个字节,aa占一个)还是3个(4+3不足补一位,占一个字节,aa占一个字节,c占一个字节)?

写个小程序验证一下:

 

int main(int argc, char* argv[])

{

int i;

test.a = 1;

test.b = 1;

test.aa = 1;

test.c = 1;

 

i=*((short *)&test);

printf("%d /n",i);

 

return 0;

}

 

输出结果是273,化为十六进制数0x111,可见是按三个字节来处理了(如果按两个字节处理的话,cba组成一个字节,是10010001(十六进制0x91)再加上aa,那就应该是0x191了)

举这个例子是为了说明一下,定义位域的话,最好是把所以有位域放在一起,这样可以节省空间(如果把c和aa换一下位置,那test就只占两个字节了)。另外也是为了强调一下位结构体的内存分配方式,按定义的先后顺序来分配,而位域(或成员)内的字节顺序则按照CPU的位顺序来进行(一般与CPU的端模式对应)。

 

 

 

struct mybitfields

{

unsigned short a : 4;

unsigned short b : 5;

unsigned short c : 7;

} test;

=> sizeof(test) == 2;

 

struct mybitfields

{

unsigned char a : 4;

unsigned char b : 5;

unsigned char c : 7;

} test;

=> sizeof(test) == 3;

 

struct mybitfields

{

unsigned char a : 4;

unsigned short b : 5;

unsigned char c : 7;

} test;

=> sizeof(test) == 6;

 

struct mybitfields

{

unsigned short a : 4;

unsigned char b : 5;

unsigned char c : 7;

} test;

=> sizeof(test) == 4;

 

struct mybitfields

{

unsigned char a : 4;

unsigned char b : 5;

unsigned short c : 7;

} test;

=> sizeof(test) == 4;

 

struct mybitfields

{

unsigned char a : 4;

unsigned int b : 5;

unsigned short c : 7;

} test;

=> sizeof(test) == 12;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章