大家好,我是嵌入式老林,從事嵌入式軟件開發(fā)多年,今天分享的內(nèi)容是C語言結構體對齊介紹,希望能對你有所幫助
摘要:最近有粉絲說在筆試的時候,經(jīng)常遇到求結構體字節(jié)數(shù)的問題,做完后不知道自己寫對了沒。這篇文章就來介紹一下結構體對齊的計算方法。不知道你們筆試的時候有沒有遇到這種題目呢?
一、字節(jié)對齊的基本概念
1.1 什么是字節(jié)對齊
在C語言中,結構是一種復合數(shù)據(jù)類型,其構成元素既可以是基本數(shù)據(jù)類型(如int、long、float等)的變量,也可以是一些復合數(shù)據(jù)類型(如數(shù)組、結構、聯(lián)合等)的數(shù)據(jù)單元。在結構中,編譯器為結構的每個成員按其自然邊界(alignment)分配空間。各個成員按照它們被聲明的順序在內(nèi)存中順序存儲,第一個成員的地址和整個結構的地址相同。
為了使CPU能夠對變量進行快速的訪問,變量的起始地址應該具有某些特性,即所謂的”對齊”。比如4字節(jié)的int型,其起始地址應該位于4字節(jié)的邊界上,即起始地址能夠被4整除。
1.2 為什么需要字節(jié)對齊
當我們在C語言中定義結構體時,編譯器會對結構體的成員進行內(nèi)存對齊,以提高訪問效率和節(jié)約內(nèi)存。如果沒有對齊的話,CPU在取數(shù)的時候,會花更多的指令周期。
一個32位系統(tǒng),假設有個整型變量的地址不是自然對齊,比如為0x00000002,則CPU取它的值需要訪問兩次內(nèi)存,第一次取從0x00000002-0x00000003的一個short,第二次取從0x00000004-0x00000005的一個short,然后組合得到所要的數(shù)據(jù);如果變量在0x00000003地址上的話則要訪問三次內(nèi)存,第一次為char,第二次為short,第三次為char,然后組合得到整型數(shù)據(jù)。而如果變量在自然對齊位置上,則只要訪問一次就可以取出數(shù)據(jù)
1.3 結構體對齊的規(guī)則
1,結構體的第一個成員永遠放在結構體起始位置偏移為0的地址
2,結構體從第二個成員,總是放在一個對齊數(shù)的整數(shù)倍數(shù)
對齊數(shù) = 編譯器默認的對齊數(shù)和變量自身大小的較小值
3,結構體總大小為最大對齊數(shù)(每個成員變量都有一個對齊數(shù))的整數(shù)倍。
4,如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數(shù)的整數(shù)倍處,結構體的整體大小就是所有最大對齊數(shù)(含嵌套結構體的對齊數(shù))的整數(shù)倍。
二、結構體對齊的計算
2.1 例子一
先來看一下這個結構體占了幾個字節(jié),說明一下,下面的例子都是在32bit系統(tǒng)上運行的
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
char c1;
int i;
char c2;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, i:%d, c2:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, i), offsetof(MyStruct, c2));
printf("addr c1:%x, i:%x, c2:%x
", &st.c1, &st.i, &st.c2);
return 0;
}
運行結果,這個結構體占12個字節(jié)
先來介紹一下offsetof()這個宏,如果想知道結構體的某個成員相對于結構體的首地址的偏移量,可通過這個宏獲取,這個宏在頭文件stddef.h中。我們看到結構體中的成員c1,i,c2分別相對于結構體的首地址偏移0,4,8
解釋:c1按1字節(jié)對齊,但i為int類型,按4字節(jié)對齊,所以不能緊跟其后,i的地址要為4的整數(shù)倍,所以在c1后空出了3字節(jié)開始存放,c2為1字節(jié)對齊,緊跟在i后面即可,這樣算的話,總字節(jié)數(shù)為9,但結構體的總大小要為最大對齊數(shù)的整數(shù)倍,這個結構體的最大對齊數(shù)就是4,所以得在c2的后面再補3個字節(jié),所以這個結構體就占用了12字節(jié)。
假設下圖左邊那一列是變量存放的地址,右邊是存放的變量
如果將上面例子的結構體成員換一下位置,結果又是怎樣的呢?
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
char c1;
char c2;
int i;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, c2:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, c2), offsetof(MyStruct, i));
printf("addr c1:%x, c2:%x, i:%x
", &st.c1, &st.c2, &st.i);
return 0;
}
運行結果:
解釋:c1和c2分別按1字節(jié)對齊,所以c2緊跟c1后面,i按4字節(jié)對齊,所以空2個字節(jié),再存放i。那么整個結構體大小為8字節(jié),也滿足是最大對齊數(shù)的整數(shù)倍。
實際上,這兩個例子不管是32bit還是64bit的系統(tǒng),結果都是一樣的,因為char類型和int類型在32bit和64bit系統(tǒng)中,占用的空間是一樣的。當然了,最好是在使用前先用sizeof測一下每種類型在當前環(huán)境中占用的大小。
因此,在實際項目開發(fā)中,如果定義的結構體有很多成員,盡可能地把同類型的成員放在一起,這樣可以節(jié)省一些空間。
2.2 例子二
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
char c1;
short s1;
char c2;
int i;
char c3;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, s1:%d, c2:%d, i:%d, c3:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, s1), offsetof(MyStruct, c2), offsetof(MyStruct, i), offsetof(MyStruct, c3));
printf("addr c1:%x, s1:%x, c2:%x, i:%x, c3:%x
", &st.c1, &st.s1, &st.c2, &st.i, &st.c3);
return 0;
}
運行結果:
解釋:c1為1字節(jié)對齊,s1為2字節(jié)對齊,地址要為2的整數(shù)倍,所以不能緊跟c1后面,得從2開始,c2是1字節(jié)對齊,i為4字節(jié)對齊,不能從地址5開始存放,否則CPU訪問的時候需要很多次,甚至可能會出錯。所以要在c2后空出3字節(jié),i從地址8開始,c3為1字節(jié)對齊,緊跟其后即可,累加起來為13個字節(jié),結構體總大小又要為最大對齊數(shù)整數(shù)倍,所以該結構體大小為16
那么,將同種類型的成員都放在一起,又占用了多少空間呢?
實際結果:
2.3 例子三
看一下帶結構體嵌套的如何計算:
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
int j;
char c;
}MyS1;
typedef struct
{
char c1;
MyS1 my_s1;
short s1;
int i;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, my_s1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, my_s1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
printf("addr c1:%x, my_s1:%x, s1:%x, i:%x
", &st.c1, &st.my_s1, &st.s1, &st.i);
return 0;
}
解釋:看了前面幾個例子的分析,相信這個結構體嵌套的大家也會,原理是一樣的。c1為1字節(jié)對齊,嵌套的結構體my_s1中的 j 為4字節(jié)對齊,地址要為4的整數(shù)倍,所以c1后要空出3個字節(jié),c為1個字節(jié),緊跟 j 后,s1為2字節(jié),在c后面空出2個字節(jié),i 是4個字節(jié),s1后面再空2個字節(jié)保持對齊,這樣的話,就是 4+4+2+2+4=16,最大對齊數(shù)是4,16也是4的整數(shù)倍。因此,這個結構體大小為16字節(jié)。有沒有很多同學會犯這個錯誤呢?
不要忽略了嵌套結構體的自身的對齊,嵌套的結構體my_s1的最大對齊數(shù)為4,因此嵌套的結構體my_s1的結構體大小要為4的整數(shù)倍,所以my_s1的結構體大小為8字節(jié),所以,這個結構體的大小為20字節(jié)
運行結果:
那么將嵌套結構體中的int類型改成double類型,又是多少呢?
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
double j;
char c;
}MyS1;
typedef struct
{
char c1;
MyS1 my_s1;
short s1;
int i;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, my_s1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, my_s1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
printf("addr c1:%x, my_s1:%x, s1:%x, i:%x
", &st.c1, &st.my_s1, &st.s1, &st.i);
return 0;
}
運行結果:
這個我就不帶著大家一步一步算了,自己算一下,看下你學會了沒
2.4 例子四
看一下帶聯(lián)合體嵌套的如何計算
#include < stdio.h >
#include < stddef.h >
int main(void)
{
typedef struct
{
char c1;
union
{
int j;
char c[8];
}MyUnion;
short s1;
int i;
}MyStruct;
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, MyUnion:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, MyUnion), offsetof(MyStruct, s1), offsetof(MyStruct, i));
printf("addr c1:%x, MyUnion:%x, s1:%x, i:%x
", &st.c1, &st.MyUnion, &st.s1, &st.i);
return 0;
}
解釋:這里要注意一點就是,聯(lián)合體的各成員共用一塊內(nèi)存空間,并且同時只有一個成員可以得到這塊內(nèi)存的使用權(對該內(nèi)存的讀寫),各變量共用一個內(nèi)存首地址。
運行結果:
個人覺得也不用刻意去記這些規(guī)則吧,重在理解,記規(guī)則的話,很久沒用了估計就忘了。多看看,多筆算算就清楚了??戳饲懊娴姆治觯鋵嵕蛢牲c,第一個就是結構體成員變量存放的地址是該變量類型的整數(shù)倍;第二點就是結構體的大小為最大對齊數(shù)的整數(shù)倍。
三、修改對齊方式
3.1 使用偽指令#pragma pack (n)
如果你不想使用編譯器的默認對齊方式,可通過以下方式修改結構體的字節(jié)對齊方式
在定義的結構體前后加上這兩條指令,n表示你想讓這個結構體按照幾字節(jié)對齊。
· 使用偽指令#pragma pack (n),C編譯器將按照n個字節(jié)對齊。
· 使用偽指令#pragma pack (),取消自定義字節(jié)對齊方式。
#include < stdio.h >
#include < stddef.h >
int main(void)
{
#pragma pack (1)
typedef struct
{
char c1;
short s1;
int i;
}MyStruct;
#pragma pack ()
MyStruct st;
printf("%d
", sizeof(MyStruct));
printf("offset c1:%d, s1:%d, i:%d
", offsetof(MyStruct, c1), offsetof(MyStruct, s1), offsetof(MyStruct, i));
printf("addr c1:%x, s1:%x, i:%x
", &st.c1, &st.s1, &st.i);
return 0;
}
運行結果:
因為已經(jīng)設定了按1字節(jié)對齊,所以就是,c1為1字節(jié),s1為2字節(jié),i 為2字節(jié),加起來就是 7 字節(jié)。
改成按2字節(jié)對齊又是多少呢?
實際就是c1后面再空了一個字節(jié)
評論