問大家一個問題:
struct STUDENT
{
char a;
int b;
}data;
如上結構體變數 data 占多少位元組?char 佔 1 位元組,int 佔 4 位元組,所以總共佔 5 位元組嗎?我們寫一個程式驗證一下:
# include <stdio.h>
struct STUDENT
{
char a;
int b;
}data;
int main(void)
{
printf("%p, %pn", &data.a, &data.b); //%p是取地址輸出控制符
printf("%dn", sizeof(data));
return 0;
}
輸出結果是:
00427E68, 00427E6C
8
我們看到 data 不是佔 5 位元組,而是佔 8 位元組。變數 a 的地址是從 00427E68 到 00427E6B,佔 4字 節;變數 b 的地址是從 00427E6C 到 00427E6F,也佔 4 位元組。b 佔 4 位元組我們能理解,但 a 是 char 型,char 型不是佔 1 位元組嗎,這裡為什麼佔 4 位元組?其實不是它佔了 4 位元組,它佔的還是 1 位元組,只不過結構體中有一個
位元組對齊的概念。
什麼叫位元組對齊?我們知道結構體是一種構造資料型別,裡面可以有不同資料型別的成員。在這些成員中,不同的資料型別所佔的記憶體空間是不同的。那麼系統是怎麼給結構體變數的成員分配記憶體的呢?或者說這些成員在記憶體中是如何儲存的呢?通過上面這個例子我們知道肯定不是順序儲存的。
那麼到底是怎麼儲存的呢?就是按位元組對齊的方式儲存的!即以結構體成員中佔記憶體最多的資料型別所佔的位元組數為標準,所有的成員在分配記憶體時都要與這個長度對齊。我們舉一個例子:我們以上面這個程式為例,結構體變數 data 的成員中佔記憶體最多的資料型別是 int 型,其占 4 位元組的記憶體空間,那麼所有成員在分配記憶體時都要與 4 位元組的長度對齊。也就是說,雖然 char 只佔 1 位元組,但是為了與 4 位元組的長度對齊,它後面的 3 位元組都會空著,即:
所謂空著其實也不是裡面真的什麼都沒有,它就同定義了一個變數但沒有初始化一樣,裡面是一個很小的、負的填充字。為了便於表達,我們就暫且稱之為空好了。
如果結構體成員為:
struct STUDENT
{
char a;
char b;
int c;
}data;
那麼這三個成員是怎麼對齊的?a 和 b 後面都是空 3 位元組嗎?不是!如果沒有 b,那麼 a 後面就空 3 位元組,有了 b 則 b 就接著 a 後面填充。即:
所以這時候結構體變數 data 仍佔 8 位元組。我們寫一個程式驗證一下:
# include <stdio.h>
struct STUDENT
{
char a;
char b;
int c;
}data;
int main(void)
{
printf("%p, %p, %pn", &data.a, &data.b, &data.c); //%p是取地址輸出控制符
printf("%dn", sizeof(data));
return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6C
8
這時我們發現一個問題:所有成員在分配記憶體的時候都與 4 位元組的長度對齊,多個 char 型別時是依次往後填充,但是 char 型後面的 int 型為什麼不緊接著後面填充?為什麼要另起一行?也就是說,到底什麼時候是接在後面填充,什麼時候是另起一行填充?
我們說,所有的成員在分配記憶體時都要與所有成員中佔記憶體最多的資料型別所占記憶體空間的位元組數對齊。假如這個位元組數為 N,那麼對齊的原則是:理論上所有成員在分配記憶體時都是緊接在前一個變數後面依次填充的,但是如果是“以 N 對齊”為原則,那麼,如果一行中剩下的空間不足以填充某成員變數,即剩下的空間小於某成員變數的資料型別所佔的位元組數,則該成員變數在分配記憶體時另起一行分配。
下面再來舉一個例子,大家覺得下面這個結構體變數data占多少位元組?
struct STUDENT
{
char a;
char b;
char c;
char d;
char e;
int f;
}data;
首先最長的資料型別佔 4 位元組,所以是以 4 對齊。然後 a 佔 1 位元組,b 接在 a 後面佔 1 位元組,c 接在 b 後面佔 1 位元組,d 接在 c 後面佔 1 位元組,此時滿 4 位元組了,e 再來就要另起一行。f 想緊接著 e 後面分配,但 e 後面還剩 3 位元組,小於 int 型別的 4 位元組,所以 f 另起一行。即該結構體變數分配記憶體時如下:
即總共佔 12 位元組。我們寫一個程式驗證一下:
# include <stdio.h>
struct STUDENT
{
char a;
char b;
char c;
char d;
char e;
int f;
}data;
int main(void)
{
printf("%p, %p, %p, %p, %p, %pn", &data.a, &data.b, &data.c, &data.d, &data.e, &data.f); //%p是取地址輸出控制符
printf("%dn", sizeof(data));
return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E70
12
現在大家應該能掌握位元組對齊的精髓了吧!下面給大家出一個題目試試掌握情況。我們將前面的結構體改一下:
struct STUDENT
{
char a;
int b;
char c;
}data;
即將原來第二個和第三個宣告交換了位置,大家看看現在 data 變數占多少位元組?沒錯,是 12 位元組。首先最長型別所佔位元組數為 4,所以是以 4 對齊。分配記憶體的時候 a 佔 1 位元組,然後 b 想緊接著 a 後面儲存,但 a 後面還剩 3 位元組,小於 b 的 4 位元組,所以 b 另起一行分配。然後 c 想緊接著 b 後面分配,但是 b 後面沒空了,所以 c 另起一行分配。所以總共 12 位元組。記憶體分配圖如下所示:
下面寫一個程式驗證一下:
# include <stdio.h>
struct STUDENT
{
char a;
int b;
char c;
}data;
int main(void)
{
printf("%p, %p, %pn", &data.a, &data.b, &data.c); //%p是取地址輸出控制符
printf("%dn", sizeof(data));
return 0;
}
輸出結果是:
00427E68, 00427E6C, 00427E70
12
我們看到,同樣三個資料型別,只不過交換了一下位置,結構體變數data所佔的記憶體空間就由8位元組變成12位元組,多了4位元組。這就告訴我們,在宣告結構體型別時,各型別成員的前後位置會對該結構體型別定義的結構體變數所佔的位元組數產生影響。沒有規律的定義會增加系統給結構體變數分配的位元組數,降低記憶體分配的效率。但這種影響對作業系統來說幾乎是可以忽略不計的!所以我們在寫程式的時候,如果有心的話,宣告結構體型別時就按成員型別所佔位元組數從小到大寫,或從大到小寫。但是如果沒有按規律書寫的話也不要緊,宣告結構體型別時並非一定要從小到大宣告,只是為了說明“位元組對齊”這個概念!而且有時候為了增強程式的可讀性我們就需要沒有規律地寫,比如儲存一個人的資訊:
struct STUDENT
{
char name[10];
int age;
char sex;
float score;
}data;
正常的思維是將“性別”放在“年齡”後面,但如果為了記憶體對齊而交換它們的位置,總讓人覺得有點彆扭。所以我說“盡量”有規律地寫!
這時又有人會提出一個問題:“上面這個結構體變數 data 中有成員 char name[10],長度最長,是 10,那是不是要以 10 對齊?”不是,char a[10] 的本質是 10 個 char 變數,所以就把它當成 10 個 char 變數看就行了。所以結構體變數 data 中成員最長型別佔 4 位元組,還是以 4 對齊。該結構體變數分配記憶體時情況如下:
name[0] |
name[1] |
name[2] |
name[3] |
name[4] |
name[5] |
name[6] |
name[7] |
name[8] |
name[9] |
空 |
空 |
age |
sex |
空 |
空 |
空 |
float |
總共 24 位元組,我們寫一個程式驗證一下:
# include <stdio.h>
struct STUDENT
{
char name[10];
int age;
char sex;
float score;
}data;
int main(void)
{
printf("%p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %p, %pn", &data.name[0], &data.name[1], &data.name[2], &data.name[3], &data.name[4], &data.name[5], &data.name[6], &data.name[7], &data.name[8], &data.name[9], &data.age, &data.sex, &data.score);
printf("%dn", sizeof(data));
return 0;
}
輸出結果是:
00427E68, 00427E69, 00427E6A, 00427E6B, 00427E6C, 00427E6D, 00427E6E,
00427E6F, 00427E70, 00427E71, 00427E74, 00427E78, 00427E7C
24