typedef struct
{
int e_int;
char e_char1;
char e_char2;
}S2;
typedef struct
{
char e_char1;
int e_int;
char e_char2;
}S3;
S2s2;
S3 s3;
你覺得這倆結構體所占內存是一樣大嗎?其實不是!
好像也沒什么啊,一不一樣大對于C語言程序員有什么所謂!
也許你還還感覺不到,上段代碼:
S2 s2[1024] = {0};
S3 s3[1024] = {0};
對于32位系統,s2的大小為8K,而s3的大小為12K,一放大,就有很明顯的區別了。
再舉個例子:
unsignedcharbytes[10]={0};
int* p = (int*)&bytes[3];
*p = 0x345678;
你覺得執行上面的代碼會發生什么情況?Warining?只是Warning么?!
以前我也沒覺得懂得這個結構體對齊或者內存對齊有多重要,直到已經從事了嵌入式開發經驗不斷積累,才慢慢體會到,這是一種很基礎的知識,就因為這個東西不常用,而出現相關的問題是非常致命的,排查起來成本非常高。
有個小伙伴,因為一個內存對齊(結構體對齊相關知識點)問題導致的偶發性Exception問題,折騰了一個多星期。
由于項目接近尾聲,出現這種問題,項目經理、老板都操心得不得了。天天不是奶茶水果,就是宵夜,把小伙伴當寶貝來哄,為的就是快速定位這個問題。
然而,他們日以繼夜的排查了一個多星期,依然一臉懵逼。
直到讓我參與進來支援,我通過仿真方式碰巧捕捉到了這種異常情況。問題的根本原因就是強制類型轉換導致的內存對齊問題。篇幅有限,這個故事,以后慢慢細講。
接下來先看看,結構體對齊的知識點。
結構體對齊,說不難吧,我研究了很多次,都沒完全記住;說難吧,理解其原因本質,就易如破竹。結構體對齊,其實其本質就是內存對齊。
什么以最大元素變量為單位,什么最小公倍數等等法則,通通都是讓你死記硬背的,沒兩天就忘了。
為什么要結構體對齊,原因就是內存要對齊,原因是芯片內存的制造限制,是制造成本約束,是內存讀取效率要求。
如果你上學的時候認真學習過微機原理,應該還記得,芯片的地址總線和數據總線這個概念吧。沒學過微機原理也沒關系,8位單片機、16位單片機和32位單片機等等,這些總得聽說過吧。
![922797ba-d1c4-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/84/wKgZomTngwSAFWWlAAAMQ79gFeQ266.png)
![92463fbc-d1c4-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/84/wKgZomTngwSACHjEAAA0LdSc39M901.png)
你以為,通過總線的方式可以隨便訪問一個地址嗎
但是,為了提高訪問速度,其設計是這樣的:
不要瞎猜,直接上代碼。每個平臺都不一樣,請讀者自行測試,以下我是基于Windows上MinGW的GCC測的。
void base_type_size(void)
{
BASE_TYPE_SIZE(void);
BASE_TYPE_SIZE(char);
BASE_TYPE_SIZE(short);
BASE_TYPE_SIZE(int);
BASE_TYPE_SIZE(long);
BASE_TYPE_SIZE(long long);
BASE_TYPE_SIZE(float);
BASE_TYPE_SIZE(double);
BASE_TYPE_SIZE(long double);
BASE_TYPE_SIZE(void*);
BASE_TYPE_SIZE(char*);
BASE_TYPE_SIZE(int*);
typedef struct
{
}StructNull;
BASE_TYPE_SIZE(StructNull);
BASE_TYPE_SIZE(StructNull*);
}
結果是:
void : 1 Byte
char : 1 Byte
short : 2 Bytes
int : 4 Bytes
long : 4 Bytes
long long : 8 Bytes
float : 4 Bytes
double : 8 Bytes
long double : 12 Bytes
void* : 4 Bytes
char* : 4 Bytes
int* : 4 Bytes
StructNull : 0 Byte
StructNull* : 4 Bytes
這些內容不用記住,不同平臺是不一樣的,使用之前,一定要親自測試驗證下。
這里先解釋下“模數”的概念:
接著看網上流傳一個表:每個特定平臺上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。
平臺 |
長度/模數 |
char |
short |
int |
long |
float |
double |
long long |
long double |
Win-32 |
長度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
模數 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
|
Linux-32 |
長度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
12 |
模數 |
1 |
2 |
4 |
4 |
4 |
4 |
4 |
4 |
|
Linux-64 |
長度 |
1 |
2 |
4 |
8 |
4 |
8 |
8 |
16 |
模數 |
1 |
2 |
4 |
8 |
4 |
8 |
8 |
16 |
本文的的例子我用的是MinGW32的GCC來測試,你猜符合上表的哪一項?
別急,再看一個例子:
typedef struct
{
int e_int;
double e_double;
}S11;
S11 s11;
STRUCT_E_ADDR_OFFSET(s11,e_int);
STRUCT_E_ADDR_OFFSET(s11, e_double);
結果是:
s11 size = 16 s11.e_int addr: 0028FF18, offset: 0
s11 size = 16 s11.e_double addr: 0028FF20, offset: 8
很明顯,上表沒有一項完全對應得上的。簡單匯總以下我測試的結果:
長度/模數 |
char |
short |
int |
long |
float |
double |
long long |
long double |
長度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
12 |
模數 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
所以,再強調一下:因為環境的差異,在你參考使用之前,請自行測試一下。
其實,這個模數是可以改變的,可以用預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。例如
typedef struct
{
char e_char;
long double e_ld;
}S14;
想知道結構圖元素內存如何對齊,其實非常簡單。其實,你只需知道當前你使用的這個系統的基本類型的sizeof是多少,然后根據這個大小做對齊排布。例如,本文一開始的例子:
typedef struct
{
int e_int;
char e_char1;
char e_char2;
}S2;
typedef struct
{
char e_char1;
int e_int;
char e_char2;
}S3;
S2s2;
S3 s3;
32位系統中,它們內存是這么對齊的:
![92ec6414-d1c4-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/84/wKgZomTngwWAYTJaAAAVXzv4no8368.png)
簡單解釋下:
S2中的元素e_int是按4字節對齊的,其地址位4整數倍,而e_char1和e_char2就按1字節對齊,緊跟其后面就可以了;
而S3中的元素e_char1是按1字節對齊的,放在最前面,而e_int是按4字節對齊的,其地址位4整數倍,所以,只能找到個+4的位置,緊接著e_char2就按1字節對齊,跟其后面就可以了。
那么sizeof(s2)和sizeof(s3)各是多少怎么算?
也很簡單,例如這個32位系統,為了提高執行效率,編譯器會讓數據訪問以4字節為單位的,所以S2里有2個字節留空,即sizeof(s2)=8,而sizeof(s3)=12。
是不是很簡單呢!
接著,來個復雜一點的:
typedef struct
{
char e_char1;
short e_short;
char e_char2;
int e_int;
char e_char3;
}S4;
S4s4;
其內存分布如下:
![92ffb9ce-d1c4-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/84/wKgZomTngwWAFqVlAAASMkPeuVU175.png)
typedef struct
{
int e_int1;
union
{
char ue_chars[9];
int ue_int;
}u;
double e_double;
int e_int2;
}SU2;
SU2su2;
得到:
typedef struct
{
int e_int;
char e_char;
}S1;
typedef struct
{
S1 e_s;
char e_char;
}SS1;
typedef struct
{
short e_short;
char e_char;
}S6;
typedef struct
{
S6 e_s;
char e_char;
}SS2;
得出結果:
![93267cb2-d1c4-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/85/wKgZomTngwWAVyneAAAZAhilERo877.png)
得出結論:結構體內的結構體,結構體內的元素并不會和結構體外的元素合并占一個對齊單元。
只要技術上面的對齊方法,這些都不難理解。如果你非要一些規則的話,我總結成這樣:首先,不推薦記憶這些條條框框的文字,以下內容僅供參考:
- 結構體的內存大小,并非其內部元素大小之和;
- 結構體變量的起始地址,可以被最大元素基本類型大小或者模數整除;
- 結構體的內存對齊,按照其內部最大元素基本類型或者模數大小對齊;
- 模數在不同平臺值不一樣,也可通過#pragma pack(n)方式去改變;
- 如果空間地址允許,結構體內部元素會拼湊一起放在同一個對齊空間;
- 結構體內有結構體變量元素,其結構體并非展開后再對齊;
- union和bitfield變量也遵循結構體內存對齊原則。
里面涉及到很多測試源碼,如果想要獲取的話,可以關注公眾號,回復"struct"即可獲得下載鏈接。
?審核編輯 :李倩
-
C語言
+關注
關注
180文章
7615瀏覽量
137855 -
代碼
+關注
關注
30文章
4837瀏覽量
69130 -
結構體
+關注
關注
1文章
130瀏覽量
10873
原文標題:結構體對齊為什么那么重要?
文章出處:【微信號:embedded_sw,微信公眾號:嵌入式軟件實戰派】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
CCS3.3 結構體成員對齊
請問在ccs4.2 中怎么設置結構體的字節對齊?
關于labview傳入參數到DLL結構體
測試結構體成員內存對齊的方式方法
固態硬盤4K對齊操作對齊的到底是什么?為什么它如此重要?
解析C語言結構體字節如何對齊
什么是結構體的字節對齊現象
![什么是<b class='flag-5'>結構</b><b class='flag-5'>體</b>的字節<b class='flag-5'>對齊</b>現象](https://file1.elecfans.com/web2/M00/AF/83/wKgaomVbEEyAPQNKAAASF9e-1y4947.jpg)
評論