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

  1. 云栖社区>
  2. 博客>
  3. 正文

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

maojunxu 2013-05-16 19:09:52 浏览700
展开阅读全文

 "数据的的对齐"
    以下内容节选自《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的倍数


 


网友评论

登录后评论
0/500
评论
maojunxu
+ 关注