CSDN博客

img jackzhang

数据对齐问题

发表于2002/7/4 9:52:00  886人阅读

发信人: law (游戏*人生), 信区: C
标  题: Re: 发句牢骚
发信站: 饮水思源 (Fri May 24 15:59:56 2002) , 转信
 
不对齐的数据存取在x86上是影响速度,
到了sparc或者MIPS上就是个bus error。
对齐原则一般是对应指令操作数据的长度。
例如*(int *)p = 1;
在MIPS上一般会被编译成sw(store word)指令
p要按照sizeof (int)对齐。
*(char *)p = 1.
对应sb指令,就不需要对齐了。


发信人: jackzhang (编程浪子), 信区: C
标  题: 对齐问题的小结和疑问
发信站: 饮水思源 (2002年05月24日21:15:32 星期五), 站内信件
 
假设所有的数据类型的长度都是2的n次方
(char[5] == 5个char)
假设编译器采用这种对齐策略:
 
"上一个"字段的地址(初始=0)
offset
 
"当前字段"
count
 
"当前"字段的类型
type(count)
 
"上一个"字段和"当前"字段之间填充长度
fillsize
 
假如满足
( offset + sizeof(type(count-1)) + fillsize) mod sizeof(type(count)) == 0
则认为是对齐的,前提当然是整个struct的基地址是"0"
如果不满足,那就在第(count)和第(count+1)字段之间填充
这些都是由编译器在编译的时候做的,但是不能保证在运行的时候
各个字段也是对齐的,关键在于struct的实际基地址
例如
struct A
{
  WORD  a1;
  DWORD a2;
};
如果&a == 2,那么a.a2就没有对齐,那么对于运行时的分配策略来说,
只要struct的基地址能被struct中最大字段的长度(2的n次方)整除,
那么在运行时也是每个字段也是对齐的.
精确的说法是所有字段长度的最小公倍数整除,因为假设所有字段的
长度都是2的n次方,但是有个问题这里:struct本身也可以看成单一字
段,例如:
struct foo
{
  WORD a1;
  WORD a2;
  WORD a3;
};
struct foo2
{
  foo f1;
  WDORD d;
};
就可以看成长度是6的字段
 
证明:用数学归纳法,
编译时的对齐等式中的offset换成base+offset,而由于base能被
最大的2的n次方整除,所以编译时的等式仍然成立.
 
对于win32的内存分配VirtualAlloc来说它的基地址必然是64K的
整数倍,那理论上用VirtualAlloc分配的struct运行时是对齐的.
因为可以认为没有比64K更大的字段类型了.
 
对于new(malloc)来说,它相当于管理VirtualAlloc分配的内存
进行内部再分配,当然分配算法不可能去找stuct中的最大的字
段长度,那对于32位计算机来说,struct基地址至少能被4整除,
 
猜想:
标志堆的使用情况的内部数据是32位的,例如某个内存块的使用
用下列结构描述
struct
{
  DWORD len;
  char  buf[1];
};
所以导致buf(也就是new返回的地址)落在32位边界上.
当然c/c++的heap的管理肯定复杂的多.
 
总之在运行的时候不一定能保证全部对齐,因为一方面不是所有的
字段长度都是1,2,4,还有其他的例如6,除非base能被所有字段的最
小公倍数整除.
 
回家的路上想起为什么会没有对齐了,因为我的struct中有变长字
段,然后一块内存含有n个struct,这样这块内存中"下一个"struct
可能会没有对齐.
struct STRING
{
  short len;
  char  buf[1];
};
 
解决的办法可以用law说的ReadWord()来做,但是效率低,一开始
我想到变为
struct STRING
{
  short len;
  char * buf;
};
然后把变长的内容放到"所有"的struct的后面,但是一方面在生
成内存存储的时候有一点繁琐(需要最后空闲位置指针),而且更
关键的是并没有解决对齐问题,换句话说,如果一个struct完全
是由WORD和DWORD组成,也不能保证"下一个"struct是对齐的,
例如
struct A
{
  WORD a;
};
 
struct B
{
  DWORD b;
};
如果内存中连续有A和B,那b.b不一定对齐.
 

因此我的解决办法是
规定struct必须全部由WORD组成(当然也可以全部由DWORD组成)
struct STRING
{
  short len;
  short xxx;
//...
  char  buf[1];
};
对于buf的实际长度必须是2的整数倍
len = strlen(str) + strlen(str) mod 2;
STRING * s = (STRING*)malloc(sizeof(STRING) + len - 1);
memcpy(s->buf, str, len);


发信人: law (游戏*人生), 信区: C
标  题: Re: 对齐问题的小结和疑问
发信站: 饮水思源 (2002年05月24日23:36:12 星期五), 站内信件
 
glibc的malloc策略大致是2*sizeof(size_t)对齐的,
这是因为它的内存头定义为这么大。
当你需要更大的
对齐字节数的时候,可以用memalign来分配,它的策略
是分配一块内存然后取对齐部分,并回收以前的零头。
一般的C库分配的内存至少是按sizeof(int)对齐的。
 
但对齐有时候会比较浪费,因为内碎片。
所以我曾经针对特定应用,
设计过更复杂的分配策略。
就是对于不需要对齐操作的小内存块用小内存头组织成
hash表来分配,
并放在单独的堆里,一般的操作还是2*sizeof(size_t)对齐,
更大的对齐操作用glibc的策略。
这其实是一种用时间换空间的策略。
当你的应用有大量零碎内存块的时候可以节省内存。
但因为内存头比较小,所以涉及一些位操作,可能会慢一点。
 

实际上编译器的对齐策略基本是一致的
在程序里找到不对齐的操作并不是一件难事,
因为有core dump。

阅读全文
0 0

相关文章推荐

img
取 消
img