前言
在研究內(nèi)存字節(jié)對齊之前不同,先通過兩個簡單的案例了解一下內(nèi)存大小占用情況:
新建一個工程刁品,創(chuàng)建一個對象:ZLObject
-
案例一
打印 malloc
結(jié)果如下:
-
案例二
添加如下屬性和方法:
打印 malloc
結(jié)果如下:
結(jié)論:
一個對象的實際內(nèi)存大小和實例大小可能會
不一樣
秘车。例如圖中的情況拯钻,實際內(nèi)存大小是32字節(jié)莽龟,而實例大小是24字節(jié)。
棧內(nèi)存
是8字節(jié)
對齊
為什么實際內(nèi)存大小和實例大小 不一樣
呢?原因是:底層在分配內(nèi)存的時候,做了 內(nèi)存對齊
图呢,下面就深入了解 內(nèi)存對齊
的原則褐桌。
什么是內(nèi)存對齊原則照雁?
還是先通過一個案例秋泄,分析內(nèi)存如何分配的。
- 創(chuàng)建幾個屬性夭拌,分別對屬性賦值魔熏,并打印其內(nèi)存情況。
- 打印
obj
的內(nèi)存
分配情況鸽扁,并分別打印其地址指向
的內(nèi)容:
通過打印可知蒜绽,obj
內(nèi)存有三塊 0x600000d283c0
、0x600000d283d0
献烦、0x600000d283e0
每塊都有兩個地址滓窍,且每塊內(nèi)存都是16字節(jié)
卖词,通過地址打印結(jié)果分析:
- 第一塊的第一個地址指向
ZLObject
巩那,說明是isa指針;第二個地址沒有打印到結(jié)果此蜈。 - 第二塊的第一個地址指向
張三
即横,對應(yīng)的是name
屬性;第二個地址指向zhang
裆赵,對應(yīng)的是nickName
屬性东囚。 - 第三塊的第一個地址指向
北京市
,對應(yīng)的是address
屬性战授;第二個地址指向空页藻,說明沒有內(nèi)容。
為什么唯獨 0x0000001600006261
這個地址沒有打印出東西呢植兰?仔細(xì)觀察發(fā)現(xiàn)份帐,其中 0x00000016
就是數(shù)字 22
,對應(yīng)的是 age
屬性楣导;0x00006261
就是 ASCII
的 a
废境,b
,對應(yīng)的就是 a
,b
屬性噩凹。
總結(jié):堆空間內(nèi)存是
16字節(jié)
對齊
內(nèi)存對齊原則
- 數(shù)據(jù)成員對?規(guī)則:結(jié)構(gòu)體(
struct
)或聯(lián)合體(union
)的數(shù)據(jù)成員巴元,第?個
數(shù)據(jù)成員放在offset
為0
的地?,以后每個數(shù)據(jù)成員存儲的起始位置
要從該成員??或者成員的?成員??(只要該成員有?成員驮宴,?如說是數(shù)組逮刨,結(jié)構(gòu)體等)的整數(shù)倍開始。不夠整數(shù)倍的補齊堵泽。(?如int
為4
字節(jié)禀忆,則要從4
的整數(shù)
倍地址開始存儲。 - 結(jié)構(gòu)體作為成員:如果?個結(jié)構(gòu)體?有某些結(jié)構(gòu)體成員落恼,則結(jié)構(gòu)體成員要從其
內(nèi)部最?元素
??的整數(shù)倍
地址開始存儲箩退。(比如struct a
?存有struct b
,b
?有char
佳谦,int
戴涝,double
等元素,那b
應(yīng)該從8
的整數(shù)倍開始存儲钻蔑。) - 收尾?作:結(jié)構(gòu)體的總??啥刻,也就是
sizeof
的結(jié)果,必須是其內(nèi)部最?成員
的整數(shù)倍
咪笑,不?的要補?可帽。
下表是各種數(shù)據(jù)類型占用內(nèi)存大小,根據(jù)對應(yīng)類型來計算結(jié)構(gòu)體中內(nèi)存大小窗怒。
通過實際的案例來理解內(nèi)存對齊原則
案例一
創(chuàng)建兩個結(jié)構(gòu)體映跟,分析其內(nèi)存分配情況:
根據(jù)內(nèi)存對齊原則,分析以上兩個結(jié)構(gòu)體的內(nèi)存分配情況扬虚,最后再打印出結(jié)果努隙。
結(jié)果與分析的一致。
案例二
創(chuàng)建嵌套結(jié)構(gòu)體辜昵,分析其內(nèi)存分配情況:
根據(jù)內(nèi)存對齊原則荸镊,分析以上兩個嵌套結(jié)構(gòu)體的內(nèi)存分配情況,最后再打印出結(jié)果堪置。
結(jié)果與分析的一致躬存。
為什么要進行內(nèi)存對齊
內(nèi)存對齊是編譯器的管轄范圍,編譯器在編譯時會為程序中的每個數(shù)據(jù)單元安排在適當(dāng)?shù)奈恢蒙弦ㄏ牵@個過程就叫內(nèi)存對齊
岭洲。
很多 CPU(如基于 Alpha,IA-64雁竞,MIPS钦椭,和 SuperH 體系的)拒絕讀取 未對齊
數(shù)據(jù)拧额。當(dāng)一個程序要求這些 CPU 讀取 未對齊
數(shù)據(jù)時,這時 CPU 會進入 異常處理狀態(tài)
并且通知程序 不能繼續(xù)
執(zhí)行彪腔。所以侥锦,如果編譯器不進行內(nèi)存對齊,那在很多平臺的上的開發(fā)將難以進行德挣。
那么恭垦,為什么這些 CPU 會拒絕讀取 未對齊
數(shù)據(jù)?是因為 未對齊
的數(shù)據(jù)格嗅,會大大降低 CPU 的性能番挺。
CPU 存取原理
程序員通常認(rèn)為內(nèi)存印象,由一個個的字節(jié)組成屯掖。
但是玄柏,你的 CPU 并 不是
以 字節(jié)
為單位存取數(shù)據(jù)的。CPU 把內(nèi)存當(dāng)成是 一塊一塊
的贴铜,塊的大小可以是 2
粪摘,4
,8
绍坝,16
字節(jié)大小徘意,因此 CPU 在讀取內(nèi)存時也是一塊一塊進行讀取的。每次內(nèi)存存取都會產(chǎn)生一個固定的 開銷
轩褐,減少內(nèi)存存取次數(shù)將提升程序的 性能
椎咧。所以 CPU 一般會以 2
/4
/8
/16
/32
字節(jié)為單位來進行存取操作。我們將上述這些存取單位也就是塊大小稱為(memory access granularity)內(nèi)存存取粒度把介。
為了說明內(nèi)存對齊背后的原理勤讽,我們通過一個例子來說明從未對齊地址
與 對齊地址
讀取數(shù)據(jù)的差異。
案例
在一個存取粒度為 8 字節(jié)的內(nèi)存中劳澄,先從地址 0 讀取 8 個字節(jié)到寄存器地技,然后從地址 3 讀取 8 個字節(jié)到寄存器。
- 當(dāng)從地址 0 開始讀取數(shù)據(jù)時秒拔,讀取
對齊地址
的數(shù)據(jù),直接通過一次
讀取就能完成飒硅。 - 當(dāng)從地址 3 開始讀取數(shù)據(jù)時砂缩,讀取
非對齊地址
的數(shù)據(jù),需要讀取兩次
數(shù)據(jù)才能完成三娩。
讀取 非對齊地址
的數(shù)據(jù)后庵芭,還要將 0-7 的數(shù)據(jù)向上偏移 3 字節(jié),將 8-F 的數(shù)據(jù)向下偏移 5 字節(jié)雀监。最后再將兩塊數(shù)據(jù)合并放入寄存器双吆。
對一個 內(nèi)存未對齊
的數(shù)據(jù)進行了這么多額外的操作眨唬,這對 CPU 的開銷很大,大大降低了CPU性能好乐。所以才會進行內(nèi)存對齊匾竿。
總結(jié)
-
棧內(nèi)存
是8字節(jié)
對齊 -
堆內(nèi)存
是16字節(jié)
對齊