交互设计在未来很有前途,不要再说是做界面的了。
分类: C/C++
2006-04-04 14:19:50
本文只讨论在编程中由于字节对齐而引起的问题的原因和解决方法,一些基本的字节对齐知识将一带而过.
1. 字节对齐简介:
网上关于字节对齐问题的讨论很多,在参考资料2中提到
"现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。"
而在参考资料1中,提到
"其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;例如上面第二个结构体变量的地址空间。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。"
我想有了这两篇资料的介绍,我们应该对字节对齐的基本知识了解的很透彻了,下面我们就看看在实际应用中我们会遇到哪些与字节对齐有关的问题.
2.字节对齐可能引起的问题及解决方法
1.多个结构共用一块共享内存时
在大型系统设计中,共享内存是我们经常要用到的.而对共享内存的分配,我们一般采用这样的方式:
头结构 |
结构体a(记录1) |
…结构体a(记录m) |
结构体b(记录1) |
…结构体b(记录n) |
实现代码为:
int size = sizeof(STRUCT_HEAD)+m*sizeof(STRUCT_A) + n*sizeof(STRUCT_B)+……
这样,下面的代码:
#include
#include
typedef struct
{
char a;
short b;
}STRUCT_A;
typedef struct
{
double c;
}STRUCT_B;
int main()
{
STRUCT_A *pa = NULL;
STRUCT_B *pb = NULL;
void* buffer = (void*)malloc(sizeof(STRUCT_A)+sizeof(STRUCT_B));
pa = (STRUCT_A*)buffer;
pa->a = 'c';
pa->b = 12;
pb = (STRUCT_B*)(pa+1);
pb->c = 1.111;
printf("sizeof a ==%d, sizeof b ==%d\n",sizeof(STRUCT_A),sizeof(STRUCT_B));
printf("a = %c,b =%d,c ==%lf",pa->a,pa->b,pb->c);
return 0;
}
在8字节对齐进行编译的情况下,sizeof(STRUCT_A)为4,sizeof(STRUCT_B)为8,不妨假设buffer的起始地址为0x0000(总是8的整数倍),那么pb的地址就应该是0x0004,而sizeof(double) = 8,这样,在intel或alpha等系列的芯片的平台上,对pb->c的访问效率将不是最高的,而在MIPS或者sparc芯片的平台上,将会产生一个总线错误。
解决方法:
首先要保证每个结构体的大小为8的整数倍,为此,我们可以在设计结构体的时候,在结构体的尾部添加一个double类型的变量,根据前面提到的结构体对齐的三个原则中的第一个,结构体的大小一定是sizeof(double)=8的整数倍。如在上面的例子中,我们可以定义
typedef struct
{
char a;
short b;
double reserved;
}STRUCT_A;
其次,在结构体内部,要使得长度大的数据类型的变量排在结构体的前面,否则在结构体内部时同样可能会造成访问某个分量时出现上述类似的情况。如:
typedef struct
{
char a;
short b;
char c;
}STRUCT_A;
则可能会造成变量b的首地址不是sizof(short)的整数倍,从而造成访问上的错误或低效。
最后,如果结构体变量必须按某种方式排列,最好能够将需要编译器对齐的部分在结构体中显式的进行对齐,从而避免上述情况的发生,也提醒维护者和阅读者注意,如
typedef struct
{
char a;
char reserved[3];
short b;
char c;
char reserved[3];
}STRUCT_A;
这样对b的访问就不会有任何问题了。
2.结构体内存拷贝
有时候我们为了编程方便起见,对一段buffer内容直接拷贝到某个结构体中,如在参考资料3中提到的对TCP协议首部处理。这在intel或alpha等系列芯片的平台上是没有问题的,但在MIPS或者sparc芯片的平台上同样存在着1中提到的问题。在这种情况下,我们只能使采用指针的偏移来获取信息,而不能采用参考资料3中提到的方法。
另一种情况是在当数据库中取出一段数据,可能包含多条记录时,如果记录对应的结构因为对齐而存在补齐的现象,则如果采用内存拷贝进行赋值时,会因为数据大小于结构体的实际大小不一致而造成数据存取的混乱。解决方法也只能是采用指针偏移来获取信息。
3. 结论:
综上所述,我们不难总结出在结构体设计中的几点原则:
1. 除非写专用于某种平台的程序,否则尽量不要使用#pragma pack(n)来指定小于8的对齐值。
2. 结构体设计尽量保证长的数据类型在前,短的数据类型在后。
3. 如果考虑到跨平台使用,对数据缓冲区中数据,如果不能创建适合的结构体时,使用最好是采用指针偏移来实现,而不要直接采用内存拷贝来实现。
参考资料:
1. http://blog.csdn.net/zlkw/archive/2005/10/06/496048.aspx
2. http://minico.blog.edu.cn/user1/4093/archives/2006/1188182.shtml
3.
4.