iOS底層原理探索 — 內存對齊&malloc源碼分析

iOS底層原理探索篇 主要是圍繞底層進行源碼分析-LLDB調試-源碼斷點-彙編調試,讓本身之後回顧複習的😀😀c++

目錄以下:算法

iOS底層原理探索 — 開篇編程

iOS底層原理探索 — alloc&init探索數組

iOS底層原理探索 — 內存對齊&malloc源碼分析xcode

iOS底層原理探索 — isa原理與對象的本質bash

如何查看對象屬性在內存中的顯示

來看下圖代碼: 架構

有四個屬性,那他們在內存中是如何顯示的呢? 咱們一塊兒來看看唄:

咱們應先給對應的屬性賦值,否則的話他們在內存中就是假的地址.由於內存是連續的,若是沒去用的話,在內存中就是野指針. 那咱們如何去查看呢?有兩種方式: 第一種方式LLDB指令:(x p):以十六進制打印這個對象的地址空間.

**第二種方式:**Debug->Debug Workflow->View Memory(shift + Command +M)

通常不推薦用第二種方式.


內存對齊的原則

先補充一點sizeof()知識:sizeof()是獲取類型的大小,它是一個運算符,而不是方法,在編譯的時候就是一個肯定的數據.app

Interger data type ILP32 size ILP64 size
char 1 byte 1 byte
BOOL,bool 1 byte 1 byte
short 2 byte 2 byte
float 4 byte 4 byte
CGFloat 8 byte 8 byte
int 4 byte 4 byte
long 4 byte 8 byte
long long 8 byte 8 byte
double 8 byte 8 byte
pointer 4 byte 8 byte
結構體 最大屬性內存的倍數 最大屬性內存的倍數

接下來咱們來看一個問題:CGPoint在內存中如何分配? CGPoint在OC中是一個結構體,結構體通常採用內存對齊的方式分配.iphone

內存對齊的原則源碼分析

  • 數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,之後每一個數據成員存儲的起始位置要從該成員大小或者子成員大小(只要該成員有子成員,好比說是數組,結構體等)的整數倍開始(好比int爲4字節,則要從4的整數倍地址開始存儲)存儲.(好比min(當前開始的位置m n) m = 9 n = 4,9不是4的整數倍,那麼要捨棄填充 9 10 11,從12(12是4的整數倍) 開始存儲).

  • 結構體做爲成員:若是一個結構裏面有某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始存儲.(struct A裏存有struct B,B裏有char,int,double等元素,那B應該從8的整數倍開始存儲.)但在肯定複合類型成員的偏移位置時則是將複合類型做爲總體看待。

  • 結構體的總大小,也就是sizeof的結果:必須是其內部最大成員的整數倍,不足的要補齊.

  • 總結:結構體的大小等於最後一個成員的偏移量加上其大小再加上末尾的填充字節數目.即:sizeof(struct) = offset(last item) + sizeof(last item) + sizeof( training padding)

    前面的地址必須是後面的地址整數倍,不是就補齊。

    整個結構體的地址必須是最大字節的整數倍。

    在肯定複合類型成員的偏移位置時則是將複合類型做爲總體看待。

結構體的sizeof() 長度用 補、偏、長結構體長度按成員最大長度對齊 結合的方式來計算有助於咱們理解內存對齊的原則. 例一:

例二:成員個數與每一個成員類型都同樣,只不過順序不同,佔內存大小就不同。

例三:複合型結構體(結構體裏嵌套結構體)

例子中的struct2 struct3 是例二中的 struct2 struct3.

那麼爲何要對齊呢?

現代計算機中內存空間都是按照byte劃分的,從理論上講彷佛對任何類型的變量的訪問能夠從任何地址開始,但實際狀況是在訪問特定類型變量的時候常常在特定的內存地址訪問,這就須要各類類型數據按照必定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。 對齊的做用和緣由:各個硬件平臺在對存儲空間的處理上有很大的不一樣。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。好比有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其餘平臺可能沒有這種狀況,可是最多見的是若是不按照適合其平臺要求對數據存放進行對齊,會在存取效率上帶來損失。好比有些平臺每次讀都是從偶地址開始,若是一個int型(假設爲32位系統)若是存放在偶地址開始的地方,那麼一個讀週期就能夠讀出這32bit,而若是存放在奇地址開始的地方,就須要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能獲得該32bit數據。顯然在讀取效率上降低不少。

對象的內存對齊

問題:一個OC對象佔據了多少內存空間呢?下面讓咱們用代碼實際驗證一下.

經過代碼咱們建立了一個NSObject對象,分別調用了class_getInstanceSize和malloc_size方法來查看佔據的內存空間。咱們看到打印輸出了8和16,這是怎麼回事呢? 咱們經過查看OC源碼可知,class_getInstanceSize是 返回類的成員變量佔據內存的大小.而malloc_size是 獲取obj指針指向內存的大小. 經過命令行將OC的main.m文件轉換成C++,生成main.cpp.

clang -rewrite-objc main.m -o main.cpp 
/***rewrite表明 重寫
   *-o表明 輸出
   *cpp表明 c++(c plus plus)
**/
複製代碼

須要注意這種方式沒有指定運行平臺和架構模式,咱們能夠經過命令行設置參數,來指定運行平臺和架構模式

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 
/***xcrun表明 xcode
   * iphoneos表明 運行在iPhone上
   *-arch表明 架構模式,arm64參數表明64位的架構模式
**/
複製代碼

生成的main.cpp 文件就是main.m轉換c++後的文件,直接拖拽到工程中,就能夠查看底層實現了。咱們在main.cpp 文件中搜索NSObjcet,能夠找到NSObjcet_IMPL(IMPL即 implementation 的縮寫,表明實現)。

能夠看到,NObject的底層實現就是一個結構體.咱們進一步查看Class

咱們發現Class其實就是一個指針,指向了objc_class類型的結構體. 那麼建立的NSObject對象obj的結構體只有一個成員:isa指針,而指針在64位架構中佔8個字節,因此第一次打印輸出了8。可是系統爲obj對象分配了16個字節的內存,第二次打印輸出16. 咱們在經過objc源碼來看一下:

經過alloc底層源碼能夠看到,若是一個對象佔據的內存小於16時,則會直接返回16,也就是說,系統在爲對象分配內存空間時,最小爲16個字節。 至此咱們知道了系統在建立一個對象的時候會爲其分配最少16字節的內存空間,其中對象的isa指針佔據8個字節。 到此處相信你們都理解,爲何打印8和16了吧. 接下來,在解釋一下爲何打印40和48? 根據前面咱們說的結構體內存對齊,相信你們很快就能知道爲啥打印40. 首先TCJTeacher繼承NSObject,內部包含一個isa佔8字節,name屬性佔8字節,age爲int修飾佔4字節,height爲long修飾佔8字節,hobby屬性佔8字節,一共佔36,又36不是8的整數倍,根據結構體內存對齊原則:整個結構體的地址必須是最大字節的整數倍,因此會打印40. 接下來咱們來分析爲何打印48?

從圖上咱們能夠看到calloc方法在 malloc源碼裏面.

malloc源碼分析

至此咱們換一份源碼分析:

以後進入calloc流程,進行具體的內存開闢,在使用calloc申請內存的過程當中,首先調用 malloc_zone_calloc方法

在其內部調用zone->calloc初始化而且返回了一個ptr指針

斷點到此處,而且打印 zone->calloc ,根據提示找到 default_zone_calloc

default_zone_calloc方法內部又看到熟悉的 zone->calloc,繼續打印獲得提示 nano_calloc

malloc的源碼中搜索 nano_calloc,於 nano_calloc.c文件中找到該方法,其中的核心代碼**_nano_malloc_check_clear**,進行內存申請,而且返回一個成熟的指針ptr

其中**_nano_malloc_check_clear 方法內部的segregated_size_to_fit**則是對象開啓內存的大小的算法,代表其實際對齊原則爲16字節對齊

這其中會在 segregated_next_block不斷遞歸去尋找合適的內存空間:

到這知道爲何會打印48了吧.

總結:

對象內存的申請按照8字節對齊,不滿16字節按照16字節計算;可是實際上calloc實際開闢內存的時候,則是進行了16字節對齊. 最後附上calloc流程圖:

相關文章
相關標籤/搜索