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
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
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)