C語言結構體
-
結構體的定義
結構體的定義要使用struct關鍵字,並以";"結尾。
下面找個微軟定義的結構體:typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME, *LPFILETIME;
可以看出在定義結構體時使用了typedef,為_FILETIME起了一個別名,並定義了指向改結構
的指針PFILETIME。 -
結構體變量的初始化與賦值操作
- 使用初始化列表進行初始化
例如:FILETIME ft = { 88,99 };
還可以使用memset進行清零初始化:FILETIME ft memset(&ft,0,sizeof(ft));
- 結構體變量賦值
C++11標准之前只能在結構體變量初始化的時候可以使用列表進行初始化,
現在支持C++11標准的編譯器可以在任意場合使用列表進行賦值,編譯時不會報錯.
例:
C++11標准還可以直接在定義結構體時為每個成員指定初值,例:
但是最后還是不要使用新標准這兩個特性,因為在不支持C++11標准的編譯器上會報錯,
- 使用初始化列表進行初始化
-
一個空結構體的大小
一個空結構體的大小為1字節,而不是零字節
例:
-
計算結構體大小
- 結構體成員對齊值
typedef struct tagTest { char m_chTest; int m_nTest; }TEST;
上面這個結構體的大小是多少呢?如果你不知道內存對齊,那么很可能認為認為其大小為5字節,
但其實這個結構體的真實大小可能為5字節,也有可能為6字節,也有可能是8字節.結構體的大小與結構體的成員對齊值有關,設置不同的成員對齊值,將使得同一個結構體有不同大小,
可以在VS的項目->屬性->C/C++->代碼生成,設置所有的結構體成員對齊值:
VC++編譯器共支持1,2,4,8,16這五個對齊值,默認對齊值為8.如果結構體成員對齊值是1,那么表示結構體
成員不對齊,在這種情況下上面例子中的TEST的大小為5.
還可以使用/Zp命令來這設置結構體成員對齊值:
但是對齊值必須是1,2,4,8,16,如果指定為其他值編譯器編譯時會給出警告,並自行選擇合理的編譯對齊值:
上面這兩種設置成員對齊值的方法是設置所有結構體的成員對齊值,但是有時我只想設置單個結構體的成員對齊值,
那么可以使用編譯預處理指令#pragma pack來設置,例如:#pragma pack(push,1) typedef struct tagTest { char m_chTest; int m_nTest; }TEST; #pragma pack(pop)
#pragma pack(push,1)中的push表示保存當前的結構體成員對齊值,然后將結構體成員對齊值設置1,
#pragma pack(pop)表示恢復結構體成員對齊值為上次保存的結構體對齊值,那么加載這兩條編譯預
處理命令間定義的結構體的成員對齊值全部為1
-
結構體大小計算
(1)設結構體成員對齊值為ZP
(2)設結構體當前數據成員對齊值zp=min(當前數據成員類型大小,ZP)
(3)設結構體自身對齊值stAlign=min(max(數據成員1類型大小,.....數據成員n類型大小),ZP)
(4)設置結構體某成員距離結構體首地址的偏移為offset
(5)每個成員的位置偏移(也就是offset)要對zp取余,如果余數不為0,則要調整位置偏移,
在大於當前偏移值中找一個最小的位置偏移,使之能夠對zp取余且余數為0,最后結構體
的總大小要對stAlign取余,如果余數不為0,采用相同的方法調整結構體大小
注意:如果結構體中有成員為數組,例如:struct tagTest { char m_ch; int m_nAry[10]; };
那么m_nAry的數據類型大小為4字節,而不是40字節 如果結構體A中有另一個結構體B作為作為結構體A的數據成員,例如:
#pragma pack(push,8) struct B { char m_ch; int m_nAry[10]; }; struct A { B m_b; char m_ch; int m_nAry[10]; }; #pragma pack(pop)
那么在結構體A中m_b的對齊值為為結構體B的自身對齊值,也就是4,在計算結構體A 的自身對齊值時,並不是將sizeof(m_b)參與計算,而是取結構體B中寬度最大的基本 數據類型所占字節參與計算,例如結構體B中寬度最大的基本數據類型為int,也就是 4字節,所以結構體A的自身對齊值為min(max(4,1,4),8)=4
例1:
#pragma pack(push,8) typedef struct tagTestA { char m_chTest; int m_nTest; float m_fTest; char m_chAry[13]; }; #pragma pack(pop)
-
結構體成員對齊值為ZP=8,結構體自身對齊值stAlign為:
min(max(sizeof(char),sizeof(int),sizeof(int),sizeof(float),sizeof(char)),ZP)=4 -
m_chTest的對齊值zp=min(sizeof(char),ZP)=1,m_chTest為結構體第一個成員,所以其相對結
構體首地址偏移量offset=0,offset對zp取余結果為0,所以無需調整,則下一個成員m_nTest相對
於結構體首地址的偏移為offset+sizeof(m_chTest)=1 -
m_nTest的對齊值zp=min(sizeof(int),ZP)=4,m_nTest相對首地址的偏移量offset=1,
offset%zp=1,取余結果不為0,當offset調整為4時,取余結果為0,則下一個成員m_fTest相對於
結構體首地址的偏移為offset+sizeof(m_nTest)=8 -
m_fTest的對齊值zp=min(sizeof(float),ZP)=4,m_nTest相對首地址的偏移量offset=8,
offset%zp=0,取余結果為0,無需調整,則下一個成員m_chAry相對於結構體首地址的偏移為
offset+sizeof(m_fTest)=12 -
m_chAry的對齊值zp=min(sizeof(char),ZP)=1,m_chAry相對首地址的偏移量offset=12,
offset%zp=0,無需調整,至此結構體tagTestA最后一個成員的偏移計算完畢,結構體大小為
offset+sizeof(m_chAry)=25, 25%stAlign != 0,所以結構體總大小需要調整,那么大於25且能
夠對4取余為0的最小值為28,所以結構體大小為28,那么下面來驗證下計算結果:
測試代碼中計算結構體偏移使用了宏函數offsetof,其定義在stddef.h中:
#if defined(_MSC_VER) && !defined(_CRT_USE_BUILTIN_OFFSETOF) #ifdef __cplusplus #define offsetof(s,m) ((size_t)&reinterpret_cast<char const volatile&>((((s*)0)->m))) #else #define offsetof(s,m) ((size_t)&(((s*)0)->m)) #endif #else #define offsetof(s,m) __builtin_offsetof(s,m) #endif
((size_t)&(((s*)0)->m)):(s*)0先是將0地址強行解釋為s類型的指針,然后(s*)0)->m引用結構體的
成員m,&(((s*)0)->m))取得成員m的內存地址,因為該指針指向0地址,所
以強轉后就得到成員m相對於結構體首地址的偏移,雖然是指向0地址的指針
但是並未使用該指針對結構體成員進行賦值和取值操作,所以不會引發崩潰。 -
例2:
#pragma pack(push,8) typedef struct tagTestA { char m_chTest; int m_nTest; float m_fTest; char m_chAry[13]; }; #pragma pack(pop) #pragma pack(push,4) struct tagTestB { double m_dbTest1; char m_chTest2; tagTestA m_stA; char m_ch[7]; }; #pragma pack(pop)
結構體tagTestB的計算有點復雜,首先它的結構體成員對齊值設置為4,其次,它的一個數據成員為結構體tagTestA類型,下面開始計算:
-
根據例1中的計算結構體tagTestA的自身對齊值為4,tagTestA的的大小為28;
tagTestB的成員對齊值ZP=4
tagTestB的自身對齊值stAlign=min(max(sizeof(double),sizeof(char),4,sizeof(char)),ZP)=4 -
m_dbTest1為結構體第一個成員,距離結構體的首地址偏移offset=0,m_dbTest1的自身對齊值
zp=min(sizeof(double),ZP)=4,offset%zp=0,所以無需調整offset,那么下一個成員m_chTest2
相對於結構體首地址的偏移為offset+sizeof(double)=8 -
根據上一步,m_chTest2距離結構體首地址的偏移offset=8,m_chTest2的自身對齊值
zp=min(sizeof(char),ZP)=1,offset%zp=0,所以offset無需調整,則下一個數據成員m_stA
距離結構體首地址的偏移為offset+sizeof(char)=9 -
根據上一步,m_stA距離結構體首地址的偏移offset=9,m_stA的自身對齊值zp=min(4,ZP)=4,
offset%zp!=0,offset需要調整,將offset調整為12時滿足offset%zp=0,則下一個成員m_ch
距離結構體首地址的偏移為offset+sizeof(m_ch)=40 -
根據上一步,m_ch距離結構體首地址的偏移offset=40,m_ch的自身對齊值為zp=min(sizeof(char),ZP)=1,
offset%zp=0,所以offset無需調整,則結構體tagTestB的大小為offset+sizeof(m_ch)=47,
且47%stAlign!=0,所以結構體大小需要調整,當結構體大小為48時,滿足要求通過以上計算可以得出m_dbTest1的偏移為0,m_chTest2的偏移為8,m_stA的偏移為12,m_ch的偏移為40,
結構體總大小為48,下面在VS中驗證結果:
注意事項:
-
以上是VC++編譯器根據不同結構體成員對齊值實現內存布局的解析,其他編譯器的實現可能有所不同
-
當結構體成員對齊值為1時,表示結構體成員不進行內存對齊,各個成員緊緊相鄰,不會有填充字節,
那么此時結構體大小為sizeof(member1)+..........sizeof(membern)