很多人在編寫代碼實作功能的時候,或多或少都會接觸到結構體的使用,在很多的編語言中,結構體都是很多資料結構的重要的組成部分。
在嵌入式的項目開發中,很多時候晶片的記憶體資源都是有限的,為了代碼更加的優化和高效,盡可能的合理使用記憶體資源,在使用結構體這類結構時,往往是要考慮結構所占的記憶體大小的。以此友善我們調整順序,盡可能的節省記憶體資源。(土豪略過,不做讨論!)
使用結構體,需要考慮結構的記憶體對齊問題,現在逐一展開講講!
1、 什麼是記憶體對齊?
我們都知道,定義的變量(元素)是要按照順序一個一個放到記憶體中去的,它們也不一定就是緊密排列的,是要按照一定的規則就行排放的,這就是記憶體對齊。
對結構體來說,元素的存儲從首位址開始,第一個元素的位址和整個結構體的首位址相同,其他的每個元素放置到記憶體中時,它都會認為記憶體是按照元素自己的大小來劃分空間的,是以元素放置在記憶體中的位置一定會在元素自己寬度(位元組數)的整數倍上開始,這就是所謂的結構體記憶體對齊問題。
特别有意思的是,C語言同意使用者自行确定記憶體對齊的設定,通過僞指令 #pragma pack (n) 可以重新設定記憶體對齊的位元組數。這個後面會講到!
2、 為什麼要有記憶體對齊?
這真是一個好問題!從網上了解到的幾個原因:
(1)考慮平台的原因。實際的硬體平台跑代碼是有所差別的,一些硬體平台可以對任意位址上的任意資料進行通路,而有一些硬體平台就不行,就是有限制,是以記憶體對齊是一種解決辦法。
(2)考慮性能的原因。CPU通路記憶體時,如果記憶體不對齊的話,為了通路到資料的話就需要幾次通路,而對齊的記憶體隻需要通路一次即可,提高了CPU通路記憶體的速度。
3、結構體的記憶體對齊規則是什麼?
每當有用到結構體的時候,總會考慮這個結構體實際應該要占用多少的記憶體,是否還有優化的空間。特别是在面試時,結構體的記憶體對齊問題是很多面試會考到,也會經常被提及問起,屬于高頻考點了!
話不多說,直接奉上結構體的記憶體對齊的判别方法,友善大家快速算出結構體所占的記憶體大小。
這裡先規定一下:記憶體對齊值稱為記憶體對齊有效值,這個值可以是1、2、4、8、16,是以先規定一下。
規則:
規則1,結構體第一個成員一定是放在結構體記憶體位址裡面的第1位。
規則2,成員對齊規則:除了第一個成員,之後的每個資料成員的對齊要按照成員自身的長度和記憶體對齊有效值進行比較,按兩者中最小的那個進行對齊,即偏移的倍數。
規則3,結構體整體對齊規則:資料成員完成對齊之後,對整個結構體的大小進行對齊。按照結構體的大小必須要是記憶體對齊有效值和結構體中最大資料成員長度兩者中的最小值的整數倍,不足的在後面補空。
4、 規則的驗證
規則已經在上面擺出來了,那怎麼知道對不對呢?那就隻能現場分析一波。
舉個例子:
編譯器記憶體對齊有效值=4,結構體如下:
typedef struct
{
int a;
char b;
}StructDef_t;
大家猜猜這個結構體占了多少個記憶體?
5個?
NO、NO、NO!!!
正确答案是:8個!
8個是怎麼來的呢?假設位址從0開始,分析如下:
首先,a為int型占4個位元組,放在最開始的位置,即offset=0的位置,放在0、1、2、3的位址。
然後,用規則2:b占一個位元組,記憶體對齊有效值為4,是以b要相對于結構體首位址的偏移要為1的倍數,放在4的位址。
最後,從上面的一步我們知道了這個結構體内的成員對齊之後占了五個位元組。用規則3:結構體内最大的成員占4個位元組,記憶體對齊有效值為4,是以整個結構體的大小要為4的倍數,5不是4的倍數,是以要在後面補齊,為8個位元組。
是以,最終這個結構體占用8個位元組!
這個過程可以用下面的圖示進行示範,友善加深了解,如下圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAnYldHL0FWby9mZvwFN4ETMfdHLkVGepZ2XtxSZ6l2clJ3LcV2Zh1Wa9M3clN2byBXLzN3btgHL9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iM2EzN5UmYkRmYmJjM5cjNzYzX1MzM1gTM2AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
5、強化訓練
如下:
#include<stdio.h>
typedef struct
{
int i;
char c1;
char c2;
}Test1;
typedef struct{
char c1;
int i;
char c2;
}Test2;
typedef struct{
char c1;
char c2;
int i;
}Test3;
int main()
{
printf("%d\n",sizeof(Test1)); // 輸出8
printf("%d\n",sizeof(Test2)); // 輸出12
printf("%d\n",sizeof(Test3)); // 輸出8
return 0;
}
這三個結構體,可以運用上面的三個規則去驗證一遍。
注意:從上面的三個結構體中可以發現,通過調換結構體裡面的資料成員的位置,可以改變結構體占空間的大小,這是因為資料元素位置不同,對齊之後的結果也不同。這種方式可以用于結構體的空間優化,通過調整元素的位置,減少記憶體的占用!
6、自定義記憶體的對齊值
C語言中是允許使用者自己定義記憶體對齊值的,使用一個僞指令即可,如下:
#pragma pack (n) // 自定義對齊值,n=1,2,4,8,16
#pragma pack ( ) // 取消自定義位元組對齊
6.1、1 位元組對齊
如下代碼:
#include<stdio.h>
#pragma pack (1)
typedef struct
{
int a;
char b;
}StructDef_t;
int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}
#pragma pack ()
在1位元組記憶體對齊情況下,這裡的結構體占5個位元組!
6.2、2 位元組對齊
如下代碼:
#include<stdio.h>
#pragma pack (2)
typedef struct
{
int a;
char b;
}StructDef_t;
int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}
#pragma pack ()
在2位元組記憶體對齊情況下,這裡的結構體占6個位元組!
6.3、4 位元組對齊
如下代碼:
#include<stdio.h>
#pragma pack (1)
typedef struct
{
int a;
char b;
}StructDef_t;
int main()
{
StructDef_t Test;
printf("a addr = %x\r\n",&Test.a);
printf("b addr = %x\r\n",&Test.b);
printf("byte = %d\r\n",sizeof(Test));
}
#pragma pack ()
在4位元組記憶體對齊情況下,這裡的結構體占8個位元組!
以此類推,可以自行驗證!