现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

说说结构体对齐

2013-07-30 06:41 工业·编程 ⁄ 共 6627字 ⁄ 字号 暂无评论

首先先来看看2个程序,第一次看会死人的。 
#include <stdio.h> 
struct test{ 
unsigned int a1; 
unsigned char a2; 
unsigned short a3; 
unsigned char a5; 
unsigned int a4; 
}; 
int main(void) 

printf("a:%d/n",sizeof(struct test)); 
return 0; 

显示16 
---------- 
#include <stdio.h> 
struct test{ 
unsigned int a1; 
unsigned char a2; 
unsigned char a5; 
unsigned short a3; 
unsigned int a4; 
}; 
int main(void) 

printf("a:%d/n",sizeof(struct test)); 
return 0; 

显示12
 
---------- 
为什么呢?? 
因为: 
unsigned char a2; 
unsigned short a3; 
unsigned char a5; 
主要是因为u16和u8的排列问题 
-------------- 
如果char后面跟的是u16,那char就是2字节存储了。 
后面的char是单字节,但是a5后面是int,所以a5就当4字节存储了。 
u8就是unsigned char 
那么u16=unsigned short 
u32=unsigned int了呵呵。 
short 16 
char 8 
int 32 
在arm和32bit的intel中是这么定义的。 
我晕,还要管哪家公司定义的。 
结构体中的排列问题 是编译器的原因还是什么? 
是 cpu架构和编译器共同作用 
--------------------------------------- 
好了,我们回头看那个程序。 
第一个: 
4+2+2+4+4=16 
4(unsigned char)+2(unsigned char因为后面的16、2个字节,所以变了)+2(unsigned short)+4(unsinged char因为后面的32、4个字节所以变了)+4(unsigned int)=16 
第二个: 
4+1+1+2+4=12 
就没怎么变。(ps: 还不知道为什么这个就没有8--〉16变成2字节,或许不用吧, 下午再问) 
在这里谢谢id=思想者的老兄了。呵呵。这么本质的问题都有。厉害。
谢谢啦,我查了一下,不知道这个东西还有这么的要求……长见识了。 
找了几个资料,弄在下面。 
Arm结构体gcc内存边界对齐问题 
这段时间移植公司的linux i386程序到Arm linux平台,本以为是件工作量很小的事情,以为只要改几个驱动程序就OK了,没想到在应用程序这一块卡了很长时间。其中最烦的事情就莫过于结构体内存边界对齐了。搞了这么久,终于终结了一些小经验。 
默认情况下,在32位cpu里,gcc对于结构体的对齐方式是按照四个字节来对齐的。看以下结构体 
typedef struct pack{ 
char a; 
int b; 
short c; 
}pack; 
对于Pack结构体,默认情况下在arm/386平台下(别的平台没试过)sizeof(pack)=12,求解过程如下: 
sizeof(char)=1; 
下一个int b,由于是四个字节,要求b的开始地址从32的整数倍开始,故需要在a后面填充3个没用的字节,记为dump(3),sizeof(b)=4,此时相当于结构体扩充为 
char a; 
char dump(3); 
int b; 
看short c,现在c的前面有8个字节,c是两个字节,c的开始地址是从16的整数开始,在b前面不需再加东西.此时对于结构体来说,sizeof(pack)=10,但是这不是最终结果,最后总的字节数也要能被4个字节整除,所以还需在short c后面再加 
dump(2); 
故总的字节数为12. 
当然以上说的只是简单的情况,下面谈谈Arm,x86在gcc里关于内存边界字节对齐的区别.对于同样的结构体,在386下 
#prama pack(1) 
后,sizeof(pack)=1 4 2=7 
而在arm下同样的操作sizeof(pack)=1 4 2 1=8,即虽然b根a之间不要填充但总的长度必须要是4的整数倍. 
在ARM 下要使结构体按指定字节对齐,可行的方法 
1.在makefile里加-fpack-struct 选项,这样的话对所有的结构按一字节对齐. 
不得不说,确实有那么些质量较差的程序可能需要你部分自然对齐,部分一字 节对齐,此时 
2. typedef struct pack{ 
}__attribute__((packed)) 
可利用__attribute__属性 
当然最后的方式,还是自己去看ARM体系结构与gcc编译选项了。 
------------------------------------------------------------------------------------------------------------ 
浅谈结构体对齐问题 
#include <stdio.h> 
int main() { 
struct ms { 
double x; 
char a; 
int y; 
}; 
// }__attribute__((packed)); 
printf("%d/n", sizeof(struct ms)); 
return 0; 

linux上运行,结果为16;如果采用注释的那一行,则结果为13 
原文:: http://dev.csdn.net/article/48/48195.shtm 
什么是内存对齐 
考虑下面的结构: 
struct foo 

char c1; 
short s; 
char c2; 
int i; 
}; 
假设这个结构的成员在内存中是紧凑排列的,假设c1的地址是0,那么s的地址就应该是1,c2的地址就是3,i的地址就是4。也就是 
c1 00000000, s 00000001, c2 00000003, i 00000004。 
可是,我们在Visual c/c++ 6中写一个简单的程序: 
struct foo a; 
printf("c1 %p, s %p, c2 %p, i %p/n", 
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a); 
运行,输出: 
c1 00000000, s 00000002, c2 00000004, i 00000008。 
为什么会这样?这就是内存对齐而导致的问题。 
为什么会有内存对齐 
以下内容节选自《Intel Architecture 32 Manual》。 
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。) 
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。 
一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。 
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16 整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。 
编译器对内存对齐的处理 
缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,上面的程序输出就变成了: 
c1 00000000, s 00000002, c2 00000004, i 00000008。 
编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。 
也正是这个原因,我们不可以断言sizeof(foo) == 8。在这个例子中,sizeof(foo) == 12。 
如何避免内存对齐的影响 
那么,能不能既达到提高性能的目的,又能节约一点空间呢?有一点小技巧可以使用。比如我们可以将上面的结构改成: 
struct bar 

char c1; 
char c2; 
short s; 
int i; 
}; 
这样一来,每个成员都对齐在其自然边界上,从而避免了编译器自动对齐。在这个例子中,sizeof(bar) == 8。 
这个技巧有一个重要的作用,尤其是这个结构作为API的一部分提供给第三方开发使用的时候。第三方开发者可能将编译器的默认对齐选项改变,从而造成这个结构在你的发行的DLL中使用某种对齐方式,而在第三方开发者哪里却使用另外一种对齐方式。这将会导致重大问题。 
比如,foo结构,我们的DLL使用默认对齐选项,对齐为 
c1 00000000, s 00000002, c2 00000004, i 00000008,同时sizeof(foo) == 12。 
而第三方将对齐选项关闭,导致 
c1 00000000, s 00000001, c2 00000003, i 00000004,同时sizeof(foo) == 8。 
如何使用c/c++中的对齐选项 
vc6中的编译选项有 /Zp[1|2|4|8|16] ,/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。也就是: 
min ( sizeof ( member ), n) 
实际上,1字节边界对齐也就表示了结构成员之间没有空洞。 
/Zpn选项是应用于整个工程的,影响所有的参与编译的结构。 
要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。 
要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令。指令语法如下: 
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n ) 
意义和/Zpn选项相同。比如: 
#pragma pack(1) 
struct foo_pack 

char c1; 
short s; 
char c2; 
int i; 
}; 
#pragma pack() 
栈内存对齐 
我们可以观察到,在vc6中栈的对齐方式不受结构成员对齐选项的影响。(本来就是两码事)。它总是保持对齐,而且对齐在4字节边界上。 
验证代码 
#include <stdio.h> 
struct foo 

char c1; 
short s; 
char c2; 
int i; 
}; 
struct bar 

char c1; 
char c2; 
short s; 
int i; 
}; 
#pragma pack(1) 
struct foo_pack 

char c1; 
short s; 
char c2; 
int i; 
}; 
#pragma pack() 
int main(int argc, char* argv[]) 

char c1; 
short s; 
char c2; 
int i; 
struct foo a; 
struct bar b; 
struct foo_pack p; 
printf("stack c1 %p, s %p, c2 %p, i %p/n", 
(unsigned int)(void*)&c1 - (unsigned int)(void*)&i, 
(unsigned int)(void*)&s - (unsigned int)(void*)&i, 
(unsigned int)(void*)&c2 - (unsigned int)(void*)&i, 
(unsigned int)(void*)&i - (unsigned int)(void*)&i); 
printf("struct foo c1 %p, s %p, c2 %p, i %p/n", 
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a, 
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a); 
printf("struct bar c1 %p, c2 %p, s %p, i %p/n", 
(unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b, 
(unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b, 
(unsigned int)(void*)&b.s - (unsigned int)(void*)&b, 
(unsigned int)(void*)&b.i - (unsigned int)(void*)&b); 
printf("struct foo_pack c1 %p, s %p, c2 %p, i %p/n", 
(unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p, 
(unsigned int)(void*)&p.s - (unsigned int)(void*)&p, 
(unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p, 
(unsigned int)(void*)&p.i - (unsigned int)(void*)&p); 
printf("sizeof foo is %d/n", sizeof(foo)); 
printf("sizeof bar is %d/n", sizeof(bar)); 
printf("sizeof foo_pack is %d/n", sizeof(foo_pack)); 
return 0; 

-----------------------------------------------------------------------------------------------------------在结构中,编译器为结构的每个成员按其自然对界条件分配空间;各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。在缺省情况下,c编译器为每一个变量或是数据单元按其自然对界条件分配空间 
例如,下面的结构各成员空间分配情况 
struct test { 
char x1; 
short x2; 
float x3; 
char x4; 
}; 
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求 4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。 
现在你知道怎么回事了吧? 
更改c编译器的缺省分配策略 
一般地,可以通过下面的两种方法改变缺省的对界条件: 
· 使用伪指令#pragma pack ([n]) 
· 在编译时使用命令行参数 
#pragma pack ([n])伪指令允许你选择编译器为数据分配空间所采取的对界策略: 
例如,在使用了#pragma pack (1)伪指令后,test结构各成员的空间分配情况就是按照一个字节对齐了 
#pragma pack(push) //保存对齐状态 
#pragma pack(1) 
#pragma pack(pop)

给我留言

留言无头像?