天天看點

unix/linux "資料的對齊" "指針的對齊" .

 "資料的的對齊"

    以下内容節選自《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的倍數

繼續閱讀