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的倍數