🔥引言
書接上文,我們了解關于結構體的基本知識,這篇將深入剖析結構體中一個重要的知識點:內存對齊
關于內存對齊是屬于熱門面試話題,對此單獨放在一篇來分享
🌈個人主頁:是店小二呀
🌈C語言筆記專欄:C語言筆記
🌈C++筆記專欄: C++筆記
🌈喜歡的詩句:無人扶我青云志 我自踏雪至山巔
文章目錄
- 一、結構體中內存對齊
- 1.1 對齊規則
- 1.2 內存對齊的意義
- 1.3 #pragma(預處理指令)
- 1.3.1 pragma相關介紹
- 1.3.2 #pragma pack(n)修改默認對齊數
- 二、結構體實現位段
- 2.1 位段的概念
- 2.2 位段的內存分配
- 2.3 位段的跨平臺問題
- 2.4 位段的應用
一、結構體中內存對齊
1.1 對齊規則
-
結構體第一個成員變量對齊相對于結構體成員地址偏移量為0的位置上
-
其他成員變量需要對齊到對齊數的整數倍
-
結構體總大小為最大對齊數的正數倍
如果存在嵌套結構體的情況,嵌套結構體占用空間需要對齊自身最大對齊數的整數倍,同時在計算結構體總大小的時候,嵌套結構體的最大對齊數參與比較
【注意】:對齊數 == 編譯器默認的一個對齊數與該成員變量大小的較小值
-
在vs環境下,系統默認對齊為8
-
在Linux中沒有默認對齊數,對齊數就是成員自身的大小
通過題目熟練的掌握以上知識.
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));--12struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));--8struct S4
{char c1;struct S2 s2;double d;
};
printf("%d\n", sizeof(struct S2));--24
【說明】:數值代表的是結構體變量地址處的偏移量
1.2 內存對齊的意義
?部分的參考資料都是這樣說的:
平臺原因(移植原因):
- 不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常
性能原因:
- 數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;對齊的內存訪問僅需要?次訪問。
假設?個處理器總是從內存中取8個字節,則地址必須是8的倍數。如果我們能保證將所有的double類型的數據的地址都對齊成8的倍數,那么就可以用一個內存操作來讀或者寫值了。否則,我們可能需要執行兩次內存訪問,因為對象可能被分放在兩個8字節內存塊中。
總體來說:結構體的內存對齊是拿空間來換取時間的做法
通過上述的觀察,不難看出。如果不存在內存對齊,需要執行兩個內存訪問(對象被分放在兩塊內存塊),而內存對齊只需要進行一次。
對此在涉及結構體時,需要考慮滿足對齊,又要節省空間。可以將占用空間小的成員盡量集中在一起
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i
};
S2 < S1
1.3 #pragma(預處理指令)
1.3.1 pragma相關介紹
- 用于指定計算機或操作系統特定的編譯器功能
- 根據定義
pragma
指令是計算機或操作系統特定的,并且通常對于每個編譯器而言都有所不同 - pragma指令可用于條件語句以提供新的預處理器功能,或為編譯器提供實現所定義的信息,
1.3.2 #pragma pack(n)修改默認對齊數
#include <stdio.h>
#pragma pack(1)//設置默認對齊數為1
struct S
{char c1;int i;char c2;
};
#pragma pacK()//取消默認對齊數,還原為默認對齊數
int main()
{printf("%d\n",sizeof(struct S));return 0;
}
推薦使用場景,在結構體進行內存對齊時,如果對于對齊方式不能達到預期,可以通過該指令更改默認對齊數
獲得該成員變量的偏移量
這里需要使用一個函數offsetof()宏,該函數被聲明在stddef.h
文件中,以下是函數offsetof()宏
size_t offsetof(type,member);
【宏定義】:
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
如果想要了解更多,可以參考下這篇博客Offsetof宏詳解-CSDN博客.這里只如何去使用Offsetof()宏計算出結構體某成員地址的偏移量。
#include <stdio.h>
#include <stddef.h>
struct S
{char a;int i;
};
int main()
{printf("%d\n",offsetof(struct S,i));//那么這里的結果就是就是4return 0;
}
【小總結】:
結構體中的內存對齊是為了以空間換取時間的做法,隨著計算機不斷地更新換代,一般不需要擔心內存空間不足的問題,逐漸地從更多考慮的是時間上的問題。同時為了節約空間的開銷,提出位段
二、結構體實現位段
2.1 位段的概念
位段是結構體的一種變形,在功能、用法上與結構體基本一致,但是在于內存分配上不同,位段可以很好的節省空間,可存在位段跨平臺的問題。同時與結構體相比有兩個點不同。
- 成員上:
int
、unsigned int
或signed int
,但是在C99中是可以選擇其他類型 - 格式上:位段成員名后面有一個冒號和一個數字
struct A
{char _a:2;char _b:5;
};
【說明】:這里數字代表的是該成員變量占用空間大小,而大小單位是比特
【問題】:位段A所占的內存大小是多大?
這個問題,需要利用下面的知識了
2.2 位段的內存分配
- 位段成員:
int
、unsigned int
、signed int
或者char
等類型(需要是整形,是要轉換為二進制) - 位段開辟空間的大小一般是以四個字節或一個字節開辟的
- 位段涉及許多不確定的因素,位段是不跨平臺的,注意可移植的程序,應該避免使用位段
struct S
{char a:3;char b:4;char c:5;char d:4;
};struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
2.3 位段的跨平臺問題
【不確定的因素大致包括】:
- 內存存放的方向是從左到右,還是從右到左
- 是低地址到高地址,還是高地址到低地址
- int類型是不確定是被當作有符號數還是無符號數
- 當一個結構體包括了兩個位段,第二個位段比較大,無法容納第一個位段剩下的空間,是舍棄還是利用剩下的空間,這是不確定的
- 位段中最大位的數目不能確定(16位機器最大16,32位機器最?32,寫成27,在16位機器會出問題),可能會沖出最大的范圍,出現問題
我們不妨以vs2013環境下測量下數據
vs2013下,位段是從左到右,從低地址到高地址,位段需要的空間不足,直接開辟一塊新的空間,我們來結合圖片理解下
【步驟】:
- 位段開辟八個bit位(這里是char類型的情況)
- 位段成員后面數字是占用多少bit位
- 根據變量數據,轉化為二級制(一個二級制為一個比特位),根據位段對應的數據,將轉為的二級制多個比特位放入
- 關于上不確定因素中(4),vs2013選擇舍棄,那就開辟一塊新的空間,重復(1,2,3)步驟
2.4 位段的應用
比如下圖中網絡協議中,在一個結構存在很多只需要幾個bit位就能實現的效果,這里使用位段就能達到想要的效果,也能節省空間的浪費。同時網絡傳輸的數據大小也會小一點,提高了網絡的流暢和效率!
【位段使用注意事項】:
struct A
{int _a:2;int _b:5;
};
int main()
{//錯誤的做法struct A s={0};scanf("%d",&s._a);//正確的示范int b=0;scanf("%d",&b);s._b=b;return 0;
}
【說明】:
位段的幾個成員共有同一個字節,而有些成員的起始位置并不是某個字節的起始位置。對此這些位置是沒有地址(內存中每個字節分配一個地址,一個字節內部的bit位是沒有地址的)
【解決辦法】:
可以將值放入一個變量中,再通過賦值給位段成員,這個賦值在以后的操作中,是很巧妙的用法的。
以上就是本篇文章的所有內容,在此感謝大家的觀看!這里是店小二C語言筆記,希望對你在學習C語言中有所幫助!