結構體的成員可以是很多的類型,結構體類型可以定義結構體類型的變量,這樣就有各種類型的成員變量。那麼,在內存中這些成員變量是如何存儲的呢?今天我把我對此的一些理解分享一下。
首先是結構體的內存對齊。
結構體的每一個成員變量的首地址要能被自身所佔的內存大小所整除,結構體遵循對齊原則,以最長的成員變量類型的長度對齊。不過,每個系統都有一個自己的默認對齊係數,我使用的RED HAT5系統的默認對齊係數是4,而我使用的64位的Ubuntu16.04系統則是8。Windows下默認對齊係數也是,最新出的系統一般是8。如果內存對齊理論與系統的默認對齊係數衝突(對齊寬度超過系統的默認對齊係數),則按系統的默認對齊係數來對齊。
例如:
struct A
{
char ch1;
char ch2;
int a;
char ch3;
}a;
printf ("sizeof(a) = %d\\n",sizeof(a)); //求結構體類型變量長度
系統的默認對齊係數可以在程序中調整,使用#pragma pack()可以調小默認對齊係數,但不可以調大,但並不建議調整。
例如:
#pragma pack(2) //將默認係數調整為2
struct A
{
char ch1;
char ch2;
int a;
char ch3;
}a;
printf ("sizeof(a) = %d\\n",sizeof(a)); //求結構體類型變量長度
為什麼要對齊呢?因為以取int型數據,32位系統是以4字節取數據的,如果不對齊,那麼可能取一個int型的數據,系統要取兩次,這影響了效率。當然,內存對齊提高了運行效率,但也犧牲了一部分空間(有空隙),這就是魚和熊掌不可兼得。
接下來分析結構體位域。
有些數據在存儲時並不需要佔用一個完整的字節,只需要佔用一個或幾個二進制位。例如開關只有通電和斷電兩種狀態,用 0 和 1 足以表示,也就是用一個二進位。基於這種考慮,C語言又提供了一種叫做位域的數據結構。在結構體定義時,我們可以指定某個成員變量所佔用的二進制位數(bit),這就是位域。
當相鄰位域成員的類型相同時,如果它們的位寬之和小於類型大小,那麼後面的成員緊鄰前一個成員存儲;如果它們的位寬之和大於類型大小,那麼後面的成員將存在下一個類型大小的空間。
例如:
struct B
{
unsigned int a:4;
unsigned int b:2;
unsigned int c:12;
}b;
printf ("sizeof(b) = %d\\n",sizeof(b));
memset (&b,0,sizeof(b)); //清空內存的值
b.a = 1;
b.b = 1;
b.c = 1;
int *p = (int *)&b; //使用指針指向結構體變量首地址,類型為int
printf ("b = %d\\n",*p); //以整型格式輸出結構體變量代表的內存中的值
當相鄰位域成員的類型不同時,不同的編譯器有不同的實現方案,GCC 會壓縮存儲,而 VC/VS 不會。我實際試驗過好多次,我發現,在GCC下,其實和上一種情況一樣。不管每個位域成員變量佔幾位,一個位域成員變量都只能存在一個它自己類型大小的空間內,比如char型的只能存在一個字節內,不可以跨字節存儲;同理int型只能存儲在4個字節內。只要存的下,它們可以緊挨在一起。
例如:
struct C
{
unsigned int a:4;
unsigned char b:2;
unsigned int c:12;
}c;
printf ("sizeof(c) = %d\\n",sizeof(c));
memset (&c,0,sizeof(c));
c.a = 1;
c.b = 1;
c.c = 1;
int *p = (int *)&c;
printf ("c = %d\\n",*p);
一旦這一個類型大小的空間存不下,就只能存在下一個類型大小的空間。
例如:
struct D
{
unsigned int a:4;
unsigned char b:5;
unsigned int c:12;
}d;
printf ("sizeof(d) = %d\\n",sizeof(d));
memset (&d,0,sizeof(d));
d.a = 1;
d.b = 1;
d.c = 1;
int *p = (int *)&d;
printf ("d = %d\\n",*p);
這也符合對齊理論。系統以一個字節大小為單位讀取char型數據,不可能對取一個數據要取兩次。
如果成員之間穿插著非位域成員,會視情況進行壓縮。即對位域成員變量壓縮,而不壓縮非位域成員變量。
例如:
struct E
{
unsigned int a:4;
unsigned char b;
unsigned int c:12;
}e;
printf ("sizeof(e) = %d\\n",sizeof(e));
memset (&e,0,sizeof(e));
e.a = 1;
e.b = 1;
e.c = 1;
int *p = (int *)&e;
printf ("e = %d\\n",*p);
位域成員可以沒有名稱,只給出數據類型和位寬,因為沒有名稱,無名位域不能使用。一般用來作填充或者調整成員位置。
例如:
struct F
{
unsigned int a:4;
unsigned int :10; //無名位域
unsigned int c:12;
};
閱讀更多 薛定諤的大臉貓 的文章