一、什麼是字節對齊,爲什麼要對齊?
現代計算機中內存空間都是按照byte劃分的,從理論上講似乎對任何類型變的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址訪問,這就需要各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。 對齊的作用和原因:各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。比如有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設爲32位系統)如果存放在偶地址開始的地方,那麼一個讀週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數據。顯然在讀取效率上下降很多。
二、分析如下代碼,談談對齊
/*測試代碼 1-1*/
#include <stdio.h>
struct Student
{
int num; //4
char name[20]; //20
char sex; //1
int age; //4
float score; //4
char addr[30]; //30
}stu1;
int main()
{
int len;
len = sizeof(stu1);
printf("%d", len); //len = 68 68 % 4 = 0
return 0;
}
此代碼,按照通常人的理解, len = 4 + 20 + 1 + 4 + 4 + 30 = 63 。但是事實上,這裏的len 爲68。這裏就是因爲struct 的對齊導致。理論就是上面的(一)這裏就不說了。
重點:因爲要struct 對齊,所以上面是68。 struct 的對齊方式,以及對齊的大小都是有規定的。struct 的最後大小必須是struct 中成員最大類型的整數倍,比如有 int , float 的,必須能被4 整除,有double 的,則必須能被8 整除。但是他的計算過程又是怎樣的呢?下面我們看看如下代碼,分析他們的struct 大小爲什麼不一樣?
/*測試代碼 1-2*/
#include <stdio.h>
typedef struct
{
char a; //1
double b; //8
int c; //4
}obj1;
typedef struct
{
char a; //1
int c; //4
double b; //8
}obj2;
int main()
{
int len1, len2;
len1 = sizeof(obj1); // len1 = 24
len2 = sizeof(obj2); // len2 = 16
printf("%d\n%d\n", len1, len2);
return 0;
}
從上面的代碼可以看出,兩個struct 的內部成員都是一樣的,一個char, 一個double, 一個int。那麼爲什麼他們的最後大小結果不一樣呢?這裏就是因爲IDE 規定各成員變量存放的起始地址相對於結構的起始地址的偏移量必須爲該變量的類型所佔用的字節數的倍數。
對齊方式(變量存放的起始地址相對於結構的起始地址的偏移量)
Char 偏移量必須爲sizeof(char)即1的倍數
Short 偏移量必須爲sizeof(short)即2的倍數
int 偏移量必須爲sizeof(int)即4的倍數
float 偏移量必須爲sizeof(float)即4的倍數
double 偏移量必須爲sizeof(double)即8的倍數
各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節IDE 會自動填充。同時IDE 爲了確保結構的大小爲結構的字節邊界數(即該結構中佔用最大空間的類型所佔用的字節數)的倍數,所以在爲最後一個成員變量申請空間後,還會根據需要自動填充空缺的字節。
那麼現在我們來分析一下上面這個代碼1-2 。
首先obj1 這個結構體。先爲第一個成員char a 分配空間,他的起始偏移量爲0,是1 的整數倍,所以直接分配1 個空間。接着爲第二個成員double b 分配空間,他的起始偏移量現在爲1(char a 佔據了1 個字節空間),他不是8 的倍數,所以要在當前偏移量的後面填充空字節,使得偏移量是8 的倍數,這裏填上7 個空字節。所以偏移量爲8,然後再加上double b 的8個字節。接着爲 int c 分配空間,他的起始偏移量爲 1(char a 的長度) + 7(空字節)+ 8(double b 的長度) = 16,所以當前偏移量是4 的整數倍,故而直接將int c 需要的4 個字節添加在後面就OK。然後得到的總長度是 1 + 7 + 8 + 4 = 20,這裏我們又要考慮一個問題,struct 的總長度必須是結構體成員中數據類型中最大長度的整數倍,這裏最大爲double 8 個字節,所以得到整個struct 的最後長度爲 24(大於20 的8 的最小倍數)。
接着我們分析obj2 這個結構體,方法完全相同,char a 分配1 個字節。接着爲 int c 分配,先添加3 個空字節,然後構成偏移量是4 的整數倍,接着爲double b 分配空間,偏移量是8 是8 的整數倍,所以直接分配空間。所以得到的大小爲:1 + 3 + 4 + 8 = 16。 因爲16 是最大類型double 的整數倍,故而obj2 的struct 長度大小爲16.
所以,別看struct 內部成員變量數量相同,類型相同,但是長度結果卻不一樣,因這還跟他們的排列順序有關。
同理測試代碼1-1 的分析也是如此:
首先int num 起始偏移量爲0,分配4 個字節空間。接着爲char name[20]分配空,考慮到char 類型長度爲1, 他的偏移量爲4,是他的整數倍,故而直接分配20 個字節空間。再爲char sex 分配空間,同理分配1 個字節空間。那麼當前偏移量爲 4 + 20 + 1 = 25.接着爲int age 分配空間,因爲偏移量是25 不是4 的整數倍,故而填3 個空字節,使得偏移量爲28, 成爲4 的整數倍,然後爲int age 分配空間。偏移量爲28 + 4 = 32 是float 類型4 的整數倍,故而直接分配空間。偏移量變爲 32 + 4 = 36.在爲char addr[30] 分配空間。得到struct 的長度爲 66. 最後考慮struct 的長度必須是整個成員變量中,最長類型的整數倍,這裏最長爲int 和float 的4 個字節。所以struct 的長度必須是4 的整數倍,故而最終struct 的長度爲 68.
typedef struct node
{
struct node *next;
}node;
這裏求出的sizeof(node) = 4; 從這裏我們知道結構體的指針大小也爲 4 個字節(struct node *next 4 個字節)。
到這裏,我們基本能搞明白struct 的對齊原理,以及計算方式。總結爲兩個步驟:
1、逐個按順序分配空間,偏移量必須是當前變量類型的整數倍,如果不是填空字節補充。
2、得到的最後長度,必須是struct 成員變量中字節最長類型的整數倍。