結構體記憶體對齊,位段,列舉+聯合
1.結構體記憶體對齊
結構體記憶體對齊規則
1.第一個成員在與結構體變數偏移量為0的地址處
2.其他成員變數要對齊到對齊數的整數倍的地址處。對齊數=編譯器預設的一個對齊數與該成員大小的 較小值
vs中預設的值為8,linux中的預設值為4
3.結構體總大小為最大對齊數(每一個成員變數都有一個對齊數)的整數倍
4.如果嵌套了結構體的情況,巢狀結構體對齊到自己最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數
(含巢狀結構體的對齊數)的整數倍
為什麼存在記憶體對齊?
1.平臺原因
不是所有的硬體平臺都能訪問任意地址上的任意資料的,某些硬體平臺只能在某些地址處取某些特定型別的資料
否則丟擲硬體異常
2.效能原因
資料結構(尤其是棧)應該儘可能地在自然邊界上對齊
原因在於,為了訪問未對齊的記憶體,處理器需要做兩次記憶體訪問,而記憶體對齊訪問僅需要一次訪問
總體來說
結構體的記憶體對齊是拿空間來換取時間的做法
2.結構體傳參
struct S { int data[10000]; int num; }; struct S s = { { 1, 2, 3, 4 }, 1000 }; //結構體傳參 void print1(struct S s) { printf("%d\n", s.num); } //結構體地址傳參 void print2(struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s); print2(&s); return 0; }
上面print1和print2函式哪個好些?
答案是首選print2函式
原因
在函式棧幀部分,我們知道函式傳參的時候,引數是需要壓棧的。如果傳遞一個結構體物件的時候,結構體過大,
引數壓棧的系統開銷比較大,所以會導致效能的下降
結論:
結構體傳參的時候,要傳結構體的地址。
3.位段
位段的宣告和結構是類似的,有兩個不同
1.位段的成員必須是int ,unsigned,int或signed int。
2.位段的成員名後邊有一個冒號和一個數字。
比如:
struct A
{
int a:2;
int b:5;
int c:6;
};
A就是一個位段型別。
位段A的大小為
printf("%d\n",sizeof(struct A));
位段的記憶體分配
1.位段的成員可以是int,unsigned int,signed int 或者是char(屬於整形家族)型別。
2.位段的空間上是按照需要以4個位元組(int)或者1個位元組(char)的方式來開闢的。
3.位段涉及很多不確定的因素,位段是不跨平臺的,注意可移植的程式應該避免使用位段。
位段的跨平臺問題
1.int位段被當成有符號數還是無符號數是不確定的。
2.位段中最大位的數目不能確定(16位機器最大是16,32位機器最大是32,寫成27,在16位機器會出現問題)
3.位段中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義
4.當一個結構包含兩個位段,第二個位段成員比較大,無法容納於第一個位段剩餘的位時,是捨棄剩餘的位還是利用,是不確定的
總結:
跟結構體相比,位段可以達到同樣的效果,但是可以很好的節省空間,但是有跨平臺的問題存在
列舉
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Color//顏色
{
RED,
GREEN,
BLUE,
};
以上定義的enum Day,enum Color都是列舉型別
{ }中的內容是列舉型別的可能取值,也叫列舉常量。這些可能取值都是有值的,預設從0開始,一次遞增1,當然在定義的時候
也可以賦初值。
列舉的優點:
1.增加程式碼的可讀性和可維護性
2.和#define定義的標示符比較列舉有型別檢查,更加嚴謹。
3.防止了命名汙染(封裝)
4.便於除錯
5.使用方便,一次可以定義多個常量
聯合(共用體)
聯合型別的定義
聯合也是一種特殊的自定義型別
這種型別定義的變數也包含一系列的成員,特徵是這些成員公用同一塊空間(所以聯合也叫共用體)
//聯合型別的宣告
union Un
{
char c;
int i;
};
//聯合變數的定義
union Un un;
//計算聯合變數的大小
printf("%d\n", sizeof(un));
聯合的特點
聯合的成員是共用同一塊記憶體空間的,這樣一個聯合變數的大小,至少是最大成員的大小(因為聯合至少得有能力儲存最大的那個成員)
聯合大小的計算
1.聯合的大小至少是最大成員的大小
2.當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍