詳解C語(yǔ)言如何計(jì)算結(jié)構(gòu)體大小(結(jié)構(gòu)體的內(nèi)存對(duì)齊)
引言:
當(dāng)我們對(duì)計(jì)算結(jié)構(gòu)體一無(wú)所知,我們不妨自己思索如何計(jì)算,是不是直接計(jì)算結(jié)構(gòu)體成員變量占用內(nèi)存的大小呢?
那我們先舉個(gè)例子
struct s1 { int i; char a; char b; }; struct s2 { char a; int i; char b; }; int main() { printf("%d\n", sizeof(struct s1)); printf("%d\n", sizeof(struct s2)); return 0; }
觀察發(fā)現(xiàn)結(jié)構(gòu)體的大小計(jì)算跟我們想的很不一樣。
不應(yīng)該是兩個(gè)char類(lèi)型,一個(gè)int類(lèi)型,2*1+4答案不應(yīng)該是6嗎?
上面兩個(gè)結(jié)構(gòu)體內(nèi)容是一樣的,只有順序不一樣,為何計(jì)算結(jié)果不一樣呢?
我們就帶著以上的疑問(wèn)去探索!
一、計(jì)算偏移量
我們要研究明白結(jié)構(gòu)體的成員列表在內(nèi)存中到底是如何存儲(chǔ)的,首先要知道結(jié)構(gòu)體的各個(gè)成員變量在內(nèi)存中相較于起始位置的偏移量。這時(shí)候要引用到offsetof,這個(gè)宏可以計(jì)算結(jié)構(gòu)體成員相較于結(jié)構(gòu)體起始位置的偏移量。
使用宏offsetof
如何使用宏offsetof?
首先有頭文件:#include<stddef.h>
參數(shù)是類(lèi)型,和成員名,返回值就是結(jié)構(gòu)體成員相較于結(jié)構(gòu)體起始位置的偏移量。
我們先試著打印下s2各個(gè)成員關(guān)于結(jié)構(gòu)體起始位置的偏移量。
發(fā)現(xiàn)結(jié)果是0、4、8,我們可以畫(huà)一張內(nèi)存圖進(jìn)行理解。
如圖所示,根據(jù)offsetof我們可以得到這樣的內(nèi)存存儲(chǔ)模式,但是這樣一共也就9個(gè)字節(jié),后面的3個(gè)字節(jié)從何而來(lái)?中間多出來(lái)的3個(gè)字節(jié)又從何而來(lái)?
我們繼續(xù)探索。
結(jié)構(gòu)體到底如何計(jì)算?
二、結(jié)構(gòu)體的對(duì)齊規(guī)則
我們經(jīng)過(guò)上面的分析,發(fā)現(xiàn)結(jié)構(gòu)體成員不是按照順序在內(nèi)存中連續(xù)存放的,而是有一定的對(duì)齊規(guī)則,接下來(lái)我們就研究結(jié)構(gòu)體的內(nèi)存規(guī)則。
- 結(jié)構(gòu)體的第一個(gè)成員永遠(yuǎn)放在相較于結(jié)構(gòu)體變量的起始未知的偏移量為0的位置
- 從第二個(gè)成員開(kāi)始,往后的每個(gè)成員都要對(duì)齊到某個(gè)對(duì)齊數(shù)的整數(shù)倍處。(對(duì)齊數(shù):結(jié)構(gòu)體成員自身大小和默認(rèn)對(duì)齊數(shù)的較小值)VS上默認(rèn)對(duì)齊數(shù)是8,gcc沒(méi)有默認(rèn)對(duì)齊數(shù),對(duì)齊數(shù)就是變量本身的大小。
- 結(jié)構(gòu)體的總大小,必須是最大對(duì)齊數(shù)的整數(shù)倍,最大對(duì)齊數(shù)是:所有成員的對(duì)齊數(shù)中最大的值
- 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
三、總結(jié)計(jì)算方法
我們首先要知道結(jié)構(gòu)體變量成員的自身字節(jié)大小,然后去尋找對(duì)齊數(shù),對(duì)齊數(shù)的尋找方法就是將自身字節(jié)大小和默認(rèn)對(duì)齊數(shù)比較,取較小值,這樣先找到對(duì)齊數(shù),然后根據(jù)自身的字節(jié)大小去填充,就完成了成員在內(nèi)存中的存儲(chǔ),最后在所有的成員已經(jīng)結(jié)束存儲(chǔ),再計(jì)算最大對(duì)齊數(shù)(所有成員的對(duì)齊數(shù)中最大值),這樣就完成了計(jì)算!
我們既然已經(jīng)知道規(guī)則和計(jì)算方法,就讓我們小試牛刀一下~
四、練習(xí)
練習(xí)一:
struct s3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct s3)); return 0; }
上面圖片的寫(xiě)法就是左邊是本身成員變量的字節(jié)大小,右邊是默認(rèn)對(duì)齊數(shù)進(jìn)行比較,最后再?gòu)膶?duì)齊數(shù)中找出最大值,就是最大對(duì)齊數(shù),所以最后0~15就是存儲(chǔ)結(jié)構(gòu)體的大小,也就是一共16個(gè)字節(jié)
練習(xí)二:
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é)構(gòu)體場(chǎng)景,結(jié)構(gòu)體S3本身大小是16,需要對(duì)齊到自身最大對(duì)齊數(shù)的位置,也就是8,然后double類(lèi)型的對(duì)齊數(shù)是8,最后總字節(jié)大小也滿(mǎn)足最大對(duì)齊數(shù),所以一共32個(gè)字節(jié)。
五、為什么存在內(nèi)存對(duì)齊?
1、平臺(tái)原因
不是所有的硬件平臺(tái)都能訪(fǎng)問(wèn)任意地址上的任意數(shù)據(jù);某些平臺(tái)只能在某些地址處取某些地址處取特定類(lèi)型的數(shù)據(jù),否則拋出硬件異常
2、性能原因
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能在自然邊界上對(duì)齊。原因在于,為了訪(fǎng)問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪(fǎng)問(wèn);而對(duì)齊的內(nèi)存訪(fǎng)問(wèn)僅需要一次訪(fǎng)問(wèn)。
總體來(lái)說(shuō)
結(jié)構(gòu)體的內(nèi)存對(duì)齊,就是讓空間換時(shí)間。
TIP:
我們?cè)谠O(shè)計(jì)結(jié)構(gòu)體時(shí),可以人為的節(jié)省空間——讓占用空間小的成員盡量集中在一起。
例如我們之前舉的例子,盡管兩個(gè)結(jié)構(gòu)體存的成員變量一樣,但是順序不一樣,結(jié)構(gòu)體內(nèi)存大小也是不同。
六、修改默認(rèn)對(duì)齊數(shù)
對(duì),你沒(méi)有聽(tīng)錯(cuò),默認(rèn)對(duì)齊數(shù)是可以修改滴,當(dāng)我們把默認(rèn)對(duì)齊數(shù)修改為1時(shí),結(jié)構(gòu)體的成員變量就是連續(xù)存儲(chǔ)的。代碼如下,計(jì)算出來(lái)的大小就是4+1+8=13
#pragma pack(1)//修改默認(rèn)對(duì)齊數(shù)為1 struct s { int a; char b; double c; }; #pragma pack()//修改默認(rèn)對(duì)齊數(shù)為默認(rèn) int main() { printf("%d\n", sizeof(struct s)); return 0; }
到此這篇關(guān)于詳解C語(yǔ)言如何計(jì)算結(jié)構(gòu)體大小(結(jié)構(gòu)體的內(nèi)存對(duì)齊)的文章就介紹到這了,更多相關(guān)C語(yǔ)言計(jì)算結(jié)構(gòu)體大小內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C語(yǔ)言程序中結(jié)構(gòu)體的內(nèi)存對(duì)齊詳解
- C語(yǔ)言中結(jié)構(gòu)體的內(nèi)存對(duì)齊規(guī)則講解
- C語(yǔ)言 詳細(xì)分析結(jié)構(gòu)體的內(nèi)存對(duì)齊
- C語(yǔ)言結(jié)構(gòu)體中內(nèi)存對(duì)齊的問(wèn)題理解
- C語(yǔ)言結(jié)構(gòu)體內(nèi)存對(duì)齊詳解
- C語(yǔ)言熱門(mén)考點(diǎn)結(jié)構(gòu)體與內(nèi)存對(duì)齊詳解
- C語(yǔ)言中結(jié)構(gòu)體與內(nèi)存對(duì)齊實(shí)例解析
- C語(yǔ)言結(jié)構(gòu)體內(nèi)存對(duì)齊問(wèn)題小結(jié)
相關(guān)文章
C語(yǔ)言中改變目錄的相關(guān)操作函數(shù)詳解
這篇文章主要介紹了C語(yǔ)言中改變目錄的相關(guān)操作函數(shù)詳解,分別是fchdir()函數(shù)和rewinddir()函數(shù)的使用方法,需要的朋友可以參考下2015-09-09C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之單鏈表存儲(chǔ)詳解
鏈表是一種物理存儲(chǔ)結(jié)構(gòu)上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過(guò)鏈表中的指針鏈接次序?qū)崿F(xiàn)的。本文將和大家一起聊聊C語(yǔ)言中單鏈表的存儲(chǔ),感興趣的可以學(xué)習(xí)一下2022-07-07C/C++ 中g(shù)cc和g++的對(duì)比與區(qū)別
這篇文章主要介紹了C/C++ 中g(shù)cc和g++的對(duì)比與區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-07-07解析bitmap處理海量數(shù)據(jù)及其實(shí)現(xiàn)方法分析
本篇文章是對(duì)bitmap處理海量數(shù)據(jù)及其實(shí)現(xiàn)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++ Boost Chrono實(shí)現(xiàn)計(jì)時(shí)碼表流程詳解
Boost是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)。Boost庫(kù)是一個(gè)可移植、提供源代碼的C++庫(kù),作為標(biāo)準(zhǔn)庫(kù)的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開(kāi)發(fā)引擎之一,是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)2022-11-11C語(yǔ)言實(shí)現(xiàn)類(lèi)似wget的進(jìn)度條效果
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)類(lèi)似wget的進(jìn)度條效果的方法,主要是讓大家可以熟練的使用轉(zhuǎn)移符\r,這里推薦給大家,需要的小伙伴參考下。2015-03-03C語(yǔ)言實(shí)現(xiàn)隨機(jī)抽獎(jiǎng)程序
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)隨機(jī)抽獎(jiǎng)程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)實(shí)例講解單鏈表的實(shí)現(xiàn)
單鏈表是后面要學(xué)的雙鏈表以及循環(huán)鏈表的基礎(chǔ),要想繼續(xù)深入了解數(shù)據(jù)結(jié)構(gòu)以及C++,我們就要奠定好這塊基石!接下來(lái)就和我一起學(xué)習(xí)吧2022-03-03Qt實(shí)現(xiàn)拖動(dòng)單個(gè)控件移動(dòng)的示例代碼
做慣了靜態(tài)圖,今天來(lái)搞一搞動(dòng)態(tài)圖吧!本文將利用Qt實(shí)現(xiàn)拖動(dòng)單個(gè)控件移動(dòng)效果,文中的示例代碼講解詳細(xì),感興趣的可以動(dòng)手嘗試一下2022-06-06