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