C語言結(jié)構(gòu)體內(nèi)存的對(duì)齊知識(shí)詳解
前言
在前面的章節(jié)中,我們談到了C語言中整數(shù)以及浮點(diǎn)數(shù)的儲(chǔ)存
今天,我們來談一談一些關(guān)于結(jié)構(gòu)體內(nèi)存的知識(shí)。
我們先來看一個(gè)例子:
struct S1 { char c1; int i; char c2; };
大家來猜猜這個(gè)結(jié)構(gòu)體S1的內(nèi)存是多少?
相信會(huì)有人給出 6 的結(jié)果,他們或許是這樣想的,兩個(gè) char 類型分別為一個(gè)字節(jié),一個(gè) int 類型又為4個(gè)字節(jié),加起來剛好為6個(gè)
但是
結(jié)果真是如此嗎?
我們來看看運(yùn)行結(jié)果:
為什么呢,接下來我們就引出正文。
一.結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
首先,正如引例所示,結(jié)構(gòu)體的內(nèi)存并不是簡(jiǎn)簡(jiǎn)單單的將結(jié)構(gòu)體各個(gè)成員的大小相加。
結(jié)構(gòu)體的大小往往遵循著結(jié)構(gòu)體的對(duì)齊規(guī)則:
- 第一個(gè)成員在與結(jié)構(gòu)體變量偏移量為0的地址處。
- 其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
- 結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)(每個(gè)成員變量都有一個(gè)對(duì)齊數(shù))的整數(shù)倍。
- 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
這里要注意的一點(diǎn)就是要解釋一下這個(gè)對(duì)齊數(shù)的概念
對(duì)齊數(shù):編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) 與 該結(jié)構(gòu)體變量成員自身大小的較小值。
注:
不是所有的編譯器都有自己默認(rèn)的對(duì)齊數(shù)。
在VS下其默認(rèn)的對(duì)齊數(shù)為8
在linux下的默認(rèn)值為4
二.怎樣計(jì)算結(jié)構(gòu)體的大小
在講計(jì)算之前,我們繼續(xù)來看一看上面的那個(gè)例子:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stddef.h> struct S1 { char c1; int i; char c2; }; int main() { printf("該結(jié)構(gòu)體成員 c1 開始的位置為第 %d 個(gè)字節(jié)\n", offsetof(struct S1, c1)); printf("該結(jié)構(gòu)體成員 i 開始的位置為第 %d 個(gè)字節(jié)\n", offsetof(struct S1, i)); printf("該結(jié)構(gòu)體成員 c2 開始的位置為第 %d 個(gè)字節(jié)\n", offsetof(struct S1, c2)); printf("該結(jié)構(gòu)體所占的內(nèi)存空間為 %d 個(gè)字節(jié)\n", sizeof(struct S1)); return 0; }
注:
宏 offsetof() 可以計(jì)算出結(jié)構(gòu)體各成員所相對(duì)開始位置的一個(gè)偏移量。
偏移量 : 我們可以理解為把結(jié)構(gòu)體變量第一個(gè)成員所儲(chǔ)存的第一個(gè)位置置于0,以此遞增
我們來看看結(jié)果:
這是為什么呢?
我們來看看上面所提到的結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則:
然后我們來看示意圖:
此時(shí),關(guān)于結(jié)構(gòu)體的大小,我們應(yīng)該清楚了不少,接下來,我們繼續(xù)來看幾道例題:
struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S2)); return 0; }
我們看到,S1與S2的區(qū)別僅僅只是調(diào)換了一下各成員間的順序,那它所占的內(nèi)存還是剛才的值嗎:
運(yùn)行結(jié)果:
我們繼續(xù)來分析一下:
趁此機(jī)會(huì),我們?cè)賮盱柟桃幌拢?/p>
struct S3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct S3)); return 0; }
它的結(jié)果會(huì)是多少呢?
不知道大家作對(duì)了嗎?
解析:
首先 double 類型占8個(gè)字節(jié)
char 又展覽接下來的一個(gè)
而 int 的對(duì)齊數(shù)為 4,所以空3個(gè)字節(jié)從12開始
而這個(gè)結(jié)構(gòu)體的最大對(duì)齊數(shù)為8
所以該結(jié)構(gòu)體占 2*8 = 16個(gè)字節(jié)
最后,我們?cè)賮砜匆坏狼短捉Y(jié)構(gòu)體的例題:
struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; }
它的結(jié)果又為多少呢?
解析:
我們先來看看規(guī)則 4: 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,
結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍
S3的最大對(duì)齊數(shù)為 8,它的大小為 16 個(gè)字節(jié)
首先,毋庸置疑的是 char 先放到首位
接下來因?yàn)镾3的對(duì)齊數(shù)為 8,所以S3放在了以位置8開始的16個(gè)字節(jié)
最后是double,對(duì)齊數(shù)為8,所以放在了24的位置
最后,該結(jié)構(gòu)體的大小為 4*8 = 32 個(gè)字節(jié)
在進(jìn)行結(jié)構(gòu)體所占大小的計(jì)算中,我們又可以得到一個(gè)基本編程常識(shí):
三.設(shè)計(jì)結(jié)構(gòu)體時(shí)要注意的方面
在我們進(jìn)行結(jié)構(gòu)體的設(shè)計(jì)中,我們可以把一些所占空間小的,來湊到一起,提高資源的利用率。
正如上文所提到的例1與例2,結(jié)構(gòu)體成員完全相同,但順序不同,兩個(gè)結(jié)構(gòu)體的大小也截然不同
//例1 struct S1 { char c1; int i; char c2; }; //例2 struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//8 return 0; }
四.為什么存在內(nèi)存對(duì)齊
對(duì)于這個(gè)原因,目前話沒有一種完全正確的答案,但是:
大部分的參考資料都是如是說的:
1. 平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;
某些硬件平臺(tái)只能在某些地址 處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2. 性能原因:
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。
原因在于,為了訪問未對(duì)齊的內(nèi)存,處理器 需要作兩次內(nèi)存訪問;而對(duì)齊的內(nèi)存訪問僅需要一次訪問。
對(duì)于原因而我們來看一個(gè)示意圖:
五.修改默認(rèn)對(duì)齊數(shù)
我們通常使用如下的預(yù)處理命令來修改編譯器的默認(rèn)對(duì)齊數(shù):
#pragma pack()
如果()里面不加數(shù)字,則默認(rèn)為編譯器的默認(rèn)對(duì)齊數(shù)
我們修改的時(shí)候,只需在()里加一個(gè)數(shù)字就行
取消的時(shí)候再添加一次#pragma pack() 即可
注:
再()里添加的數(shù)字,我們通常加的都是2的多少次方
下面來舉一個(gè)實(shí)例:
#include <stdio.h> #pragma pack(8)//設(shè)置默認(rèn)對(duì)齊數(shù)為8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消設(shè)置的默認(rèn)對(duì)齊數(shù),還原為默認(rèn) #pragma pack(1)//設(shè)置默認(rèn)對(duì)齊數(shù)為8 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消設(shè)置的默認(rèn)對(duì)齊數(shù),還原為默認(rèn) int main() { //輸出的結(jié)果是什么? printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//6 return 0; }
由此可見,我們也可以通過修改默認(rèn)對(duì)齊數(shù)來節(jié)約 結(jié)構(gòu)體使用的空間。
關(guān)于結(jié)構(gòu)體內(nèi)存的講解便到此為止。
筆者水平有限,若有錯(cuò)誤之處,還望多多指正。
總結(jié)
到此這篇關(guān)于C語言結(jié)構(gòu)體內(nèi)存對(duì)齊的文章就介紹到這了,更多相關(guān)C語言結(jié)構(gòu)體內(nèi)存對(duì)齊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++編寫DLL動(dòng)態(tài)鏈接庫的步驟與實(shí)現(xiàn)方法
這篇文章主要介紹了C++編寫DLL動(dòng)態(tài)鏈接庫的步驟與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了C++導(dǎo)出類文件及生成與調(diào)用DLL動(dòng)態(tài)連接庫的相關(guān)操作技巧,需要的朋友可以參考下2016-08-08C++實(shí)現(xiàn)LeetCode(347.前K個(gè)高頻元素)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(347.前K個(gè)高頻元素),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C語言實(shí)現(xiàn)文本文件/二進(jìn)制文件格式互換
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)文本文件和二進(jìn)制文件格式互換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03關(guān)于vector迭代器失效的幾種情況總結(jié)
下面小編就為大家?guī)硪黄P(guān)于vector迭代器失效的幾種情況總結(jié)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12C語言中用棧+隊(duì)列實(shí)現(xiàn)隊(duì)列中的元素逆置
這篇文章主要介紹了C語言中用利用棧和隊(duì)列實(shí)現(xiàn)隊(duì)列中的元素逆置的相關(guān)資料,對(duì)正在學(xué)習(xí)的小伙伴有一定的參考價(jià)值,需要的可以參考一下,希望對(duì)你有所幫助2022-02-02