您的位置:首页 > 运维架构 > Linux

unix/linux "数据的对齐" "指针的对齐" .

2013-05-16 19:09 302 查看
"数据的的对齐"

以下内容节选自《Intel Architecture 32 Manual》。

字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)

无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇

数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,

这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),

然而,需要额外的内存总线周期来访问内存中未对齐的数据。

a) 以下在hp64机上验证出数据对齐的结果

char ci=1 ci的地址是1 的倍数

short si=1 si的地址是2 的倍数

int ii=1 ii的地址是4 的倍数

long li=1 li的地址是8 的倍数

b) 以下是一个内存不对齐的例子:

unsigned char opc_2[]={0x40};

op1_1=(unsigned short *)opc_2; 可以给op1_1进行任何地址引用,如果不对齐,在引用值时发生错误致命COREDUMP.

printf("op1_1=%p/n",*op1_1);

如果地址刚好是2的倍数,在以上能过执行,否则COREDUMP(在引用时产生printf).

说明:char型的地址因为是1的倍数,所以可能是2的倍数,4的倍数.....他是一个随机的.

如果刚好符合程序,如上转化为

“指针的对齐”问题。

CPU一般要求指针的值(内存地址)要与它的指向类型数据的尺寸相匹配。例如,2个字节的数据类型被访问的地址值为 2 的倍数,

4个字节的数据类型(如 int)被访问的地址值是 4 的倍数,等等。一个字节的数据类型(如 char 型)对其访问地址无限制(因为是 1 的倍数)。

在Intel处理器上,指针对齐这个问题不是致命的,至多占用CPU更多的时间进行指针转换,从而带来性能的下降;

但是对于其它类型的处理器来说就是致命的了:如果访问的指针不对齐,会带来运行错误。

当指针从一种类型的指针转换到另外一种类型的指针的时候,就存在着产生非对齐指针的可能性。不过,正如原文中所说的,

“一个对齐要求较高的指针类型S转换成一个对齐要求较低的指针类型D是安全的”,这种转换不会产生非对齐指针;但是,

如果是一个对齐要求较低的指针类型D转换成一个对齐要求较高的指针类型S,就有可能产生非对齐指针,这种转换就是不安全。

<<c语言参考手册>>中6.1.3节中提到,把一个对齐要求较高的指针类型S转换成一个对齐要求较低的指针类型D是安全的,

安全指的是类型D在用于读取与存储D类型对象时可以得到预期效果,后面转换回原指针类型时能够恢复原指针.

举例说明:

1) 较高的指针类型到较低的指针类型转换

在unix上,每个int型数据占4个字节,在hp系统上,例如,十六进制表示的整数 0x1a2b3c4d 在内存中是这样存放的:

(高存储地址)

Base Address +0 1a

Base Address +1 2b

Base Address +2 3c

Base Address +3 4d

(低存储地址)

如果有这样的程序:

代码:

int a = 0x1a2b3c4d;

int *p = &a;

char *q = (char *)p;

printf("%p/n", *q); //执行结果:000000000000001a

p=NULL;

p = (int*)q;

printf("%p/n", *p); //执行结果:000000001a2b3c4d

则其中的指针 p 和 q 的值就是 Base Address。q 是char型指针(重要),所以 *q 的结果得到 0x1a不就是我们期待的吗?

在这种情况下,不会得到 0x1a 以外的值,所以是也可以说是“安全”的。

*****重要:搞清楚指针操作受指针“基类型”而不是指针“所指向的对象类型”支配 就没问题了****

无论对于自己的程序还是系统来说。程序后面两句说明,从 q 指针能够恢复原来的 p 指针,从结果来看也能得到我们预期的值。

1) 较低的指针类型到较高的指针类型转换

把一个对齐要求较低的指针类型D转换成一个对齐要求较高的指针类型S是不安全的,得不到预期值。例如,char* 到 int*的转换:

代码:

char c = 0x1a;

char* p = &c;

int* q = (int*)p;

printf("%p/n", *q); //执行结果:000000001a000000

p=NULL;

p = (char*)q;

printf("%p/n", *p); //执行结果:000000000000001a

你无法预测(预期)打印出的 *q 的值是什么,因为我们除了知道整数的一个低位字节(0x1a)之外,对于这个字节后面的其余三个字节位一无所知,

其值是不确定的。因此,这样的指针转换就不是安全的(更严重的情况是用 *q 写数据,会破坏掉 0x1a 后面的三个字节的数据,给程序带来错误隐患),

其结果也是不能预测的。通过 q 恢复原来的指针 p 没有问题。

另外说明:

对于char型数组可以自然对其

例如: char[9],地址是8的倍数 可以把它的值赋给LONG型数据。

char[5],地址是4的倍数

char[3],地址是2的倍数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: