探尋OC對象的本質,咱們平時編寫的Objective-C代碼,底層實現其實都是C\C++代碼。 那麼一個OC對象佔用多少內存呢?看完這篇文章你將瞭解OC對象的內存佈局和內存分配機制。linux
使用的代碼下載 要用的工具:c++
首先咱們使用最基本的代碼驗證對象是什麼?git
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *obj=[[NSObject alloc]init];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
使用clang
編譯器編譯成cpp
, 執行clang -rewrite-objc main.m -o main.cpp
以後生成的cpp
,這個生成的cpp
咱們不知道是跑在哪一個平臺的,如今咱們指定iphoeos
和arm64
從新編譯一下。xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main64.cpp
,將main64.cpp
拖拽到Xcode中並打開。github
clang | 編譯器 |
---|---|
xcrun | 命令 |
sdk | 指定編譯的平臺 |
arch | arm64架構 |
-rewrite-objc | 重寫 |
main.m | 重寫的文件 |
main64.cpp | 導出的文件 |
-o | 導出 |
command + F
查找int main
,找到關鍵代碼,這就是main
函數的轉化成c/c++
的代碼:xcode
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj=((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_1b47c1_mi_0);
}
return 0;
}
複製代碼
而後搜索bash
struct NSObject_IMPL {
Class isa;
};
複製代碼
那麼這個結構體是什麼呢? 其實咱們Object-C
編譯以後對象會編譯成結構體,如圖所示: 架構
isa
是什麼嗎?經過查看源碼得知:
typedef struct objc_class *Class;
複製代碼
class
實際上是一個指向結構體的指針,而後com+點擊class
獲得:app
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
class
是一個指針,那麼佔用多少內存呢?你們都知道指針在32位是4字節,在64位是8字節。iphone
NSObject *obj=[[NSObject alloc]init];
複製代碼
能夠理解成實例對象是一個指針,指針佔用8或者4字節,那麼暫時假設機器是64位,記爲對象佔用8字節。 obj
就是指向結構體class
的一個指針。 那麼咱們來驗證一下:函數
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *obj=[[NSObject alloc]init];
//得到NSobject對象實例大小
size_t size = class_getInstanceSize(obj.class);
//獲取NSObjet指針的指向的內存大小
//須要導入:#import <malloc/malloc.h>
size_t size2 = malloc_size((__bridge const void *)(obj));
NSLog(@"size:%zu size2:%zu",size,size2);
}
return 0;
}
複製代碼
得出結果是:
size:8 size2:16
複製代碼
結論是:指針是8字節,指針指向的的內存大小爲16字節。 查看源碼得知[[NSObject alloc]init]
的函數運行順序是:
class_createInstance
-_class_createInstanceFromZone
複製代碼
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
**
size_t size = cls->instanceSize(extraBytes);
**
return obj;
}
複製代碼
這個函數前邊後邊省略,取出關鍵代碼,其實size
是cls->instanceSize(extraBytes)
執行的結果。那麼咱們再看下cls->instanceSize
的源碼:
//成員變量大小 8bytes
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
複製代碼
能夠經過源碼註釋得知:CF要求全部的objects 最小是16bytes。
class_getInstanceSize
函數的內部執行順序是class_getInstanceSize->cls->alignedInstanceSize()
查閱源碼:
//成員變量大小 8bytes
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
複製代碼
因此最終結論是:對象指針實際大小爲8bytes,內存分配爲16bytes,實際上是空出了8bytes。
驗證: 在剛纔 的代碼打斷點和設置Debug->Debug Workflow->View Memory
,而後運行程序,
obj->view *objc
獲得上圖所示的內存佈局,從
address
看出和
obj
內存同樣,左上角是16字節,8個字節有數據,8個字節是空的,默認是0.
使用lldb命令memory read 0x100601f30
輸出內存佈局,以下圖:
x/4xg 0x100601f30
輸出:
x/4xg 0x100601f30
中
4
是輸出
4
個數據,
x
是16進制,後邊
g
是8字節爲單位。能夠驗證剛纔的出的結論。
那麼咱們再使用複雜的一個對象來驗證:
@interface Person : NSObject
{
int _age;
int _no;
}
@end
複製代碼
使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main64.cpp
編譯以後對應的源碼是:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;// 8 bytes
int _age;//4 bytes
int _no;//4 bytes
};
複製代碼
Person——IMPL
結構體佔用16bytes
Person *obj=[[Person alloc]init];
obj->_age = 15;
obj->_no = 14;
複製代碼
使用代碼驗證:
Person *obj=[[Person alloc]init];
obj->_age = 15;
obj->_no = 14;
struct Person_IMPL *p =(__bridge struct Person_IMPL*)obj;
NSLog(@"age:%d no:%d",p->_age,p->_no);
//age:15 no:14
複製代碼
使用內存佈局驗證:
Person
佔用16 bytes。
下邊是一個直觀的內存佈局圖:
@interface Person : NSObject
{
@public
int _age;//4bytes
}
@end
@implementation Person
@end
//Student
@interface Student : Person
{
@public
int _no;//4bytes
}
@end
@implementation Student
@end
複製代碼
那小夥伴可能要說這必定是32字節,由於Person
上邊已經證實是16字節,Student
又多了個成員變量_no
,因爲內存對齊,必定是16的整倍數,那就是16+16=32字節。 其實否則,Person
是內存分配16字節,其實佔用了8+4=12字節,剩餘4字節位子空着而已,Student
是一個對象,不可能在成員變量和指針中間有內存對齊的,參數和指針是對象指針+偏移量得出來的,多個不一樣的對象纔會存在內存對齊。因此Student
是佔用了16字節。
那麼咱們來證實一下:
Student *obj=[[Student alloc]init];
obj->_age = 6;
obj->_no = 7;
//得到NSobject對象實例成員變量佔用的大小 ->8
size_t size = class_getInstanceSize(obj.class);
//獲取NSObjet指針的指向的內存大小 ->16
size_t size2 = malloc_size((__bridge const void *)(obj));
NSLog(@"size:%zu size2:%zu",size,size2);
//size:16 size2:16
複製代碼
再看一下LLDB查看的內存佈局:
(lldb) x/8xw 0x10071ae30
0x10071ae30: 0x00001299 0x001d8001 0x00000006 0x00000007
0x10071ae40: 0xa0090000 0x00000007 0x8735e0b0 0x00007fff
(lldb) memory read 0x10071ae30
0x10071ae30: 99 12 00 00 01 80 1d 00 06 00 00 00 07 00 00 00 ................
0x10071ae40: 00 00 09 a0 07 00 00 00 b0 e0 35 87 ff 7f 00 00 ..........5.....
(lldb) x/4xg 0x10071ae30
0x10071ae30: 0x001d800100001299 0x0000000700000006
0x10071ae40: 0x00000007a0090000 0x00007fff8735e0b0
複製代碼
能夠看出來0x00000006
和0x00000007
就是兩個成員變量的值,佔用內存是16字節。
咱們將Student
新增一個成員變量:
//Student
@interface Student : Person
{
@public
int _no;//4bytes
int _no2;//4bytes
}
@end
@implementation Student
@end
複製代碼
而後查看內存佈局:
(lldb) x/8xg 0x102825db0
0x102825db0: 0x001d8001000012c1 0x0000000700000006
0x102825dc0: 0x0000000000000000 0x0000000000000000
0x102825dd0: 0x001dffff8736ae71 0x0000000100001f80
0x102825de0: 0x0000000102825c60 0x0000000102825890
複製代碼
從LLDB
能夠看出來,內存變成了32字節。(0x102825dd0-0x102825db0=0x20)
咱們再增長一個屬性看下:
@interface Person : NSObject
{
@public
int _age;//4bytes
}
@property (nonatomic,assign) int level; //4字節
@end
@implementation Person
@end
//InstanceSize:16 malloc_size:16
複製代碼
爲何新增了一個屬性,內存仍是和沒有新增的時候同樣呢? 由於property
=setter
+getter
+ivar
,method
是存在類對象中的,因此實例Person
佔用的內存仍是_age
,_level
和一個指向類的指針,最後結果是4+4+8=16bytes
。
再看下成員變量是3個的時候是多少呢?看結果以前先猜想一下:三個int
成員變量是12,一個指針是8,最後是20,因爲內存是8的倍數,因此是24。
@interface Person : NSObject
{
@public
int _age;//4bytes
int _level;//4bytes
int _code;//4bytes
}
@end
@implementation Person
@end
Person *obj=[[Person alloc]init];
obj->_age = 6;
//得到NSobject對象實例成員變量佔用的大小 ->24
Class ocl = obj.class;
size_t size = class_getInstanceSize(ocl);
//獲取NSObjet指針的指向的內存大小 ->32
size_t size2 = malloc_size((__bridge const void *)(obj));
printf("InstanceSize:%zu malloc_size:%zu \n",size,size2);
InstanceSize:24 malloc_size:32
複製代碼
爲何和咱們猜想的不同呢? 那麼咱們再探究一下: 實例對象佔用多少內存,固然是在申請內存的時候建立的,則查找源碼NSObject.mm 2306行
獲得建立對象函數調用順序allocWithZone->_objc_rootAllocWithZone->_objc_rootAllocWithZone->class_createInstance->_class_createInstanceFromZone->_class_createInstanceFromZone
最後查看下_class_createInstanceFromZone
的源碼,其餘已省略,只留關鍵代碼:
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
**
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
**
obj = (id)calloc(1, size);
**
return obj;
}
複製代碼
那麼咱們在看一下instanceSize
中的實現:
//對象指針的大小
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
複製代碼
最後調用的obj = (id)calloc(1, size);
傳進去的值是24,可是結果是申請了32字節的內存,這又是爲何呢? 由於這是c
函數,咱們去蘋果開源官網下載源碼看下,能夠找到這句代碼:
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
複製代碼
看來NANO_MAX_SIZE
在申請空間的時候作完優化就是16的倍數,而且最大是256。因此size = 24 ;obj = (id)calloc(1, size);
申請的結果是32字節。 而後再看下Linux
空間申請的機制是什麼? 下載gnu資料, 獲得:
#ifndef _I386_MALLOC_ALIGNMENT_H
#define _I386_MALLOC_ALIGNMENT_H
#define MALLOC_ALIGNMENT 16
#endif /* !defined(_I386_MALLOC_ALIGNMENT_H) */
/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks. It must be a power of two at least 2 * SIZE_SZ, even on machines for which smaller alignments would suffice. It may be defined as larger than this though. Note however that code and data structures are optimized for the case of 8-byte alignment. */ //最少是2倍的SIZE_SZ 或者是__alignof__(long double) #define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \ ? __alignof__ (long double) : 2 * SIZE_SZ) /* The corresponding word size. */ #define SIZE_SZ (sizeof (INTERNAL_SIZE_T)) #ifndef INTERNAL_SIZE_T # define INTERNAL_SIZE_T size_t #endif 複製代碼
在i386中是16,在其餘系統中按照宏定義計算, __alignof__ (long double)
在iOS中是16,size_t
是8,則上面的代碼簡寫爲#define MALLOC_ALIGNMENT (2*8 < 16 ? 16:2*8)
最終是16字節。
總結:
實例對象實際上是結構體,佔用的內存是16的倍數,最少是16,因爲內存對齊,實際使用的內存爲M,則實際分配內存爲(M%16+M/16)*16。實例對象的大小不受方法影響,受實例變量影響。