總所周知,oc對(duì)象底層是由結(jié)構(gòu)體實(shí)現(xiàn)的,所以通過分析結(jié)構(gòu)體內(nèi)存占用情況可以更好的理解oc對(duì)象的內(nèi)存占用术瓮。
1.把OC對(duì)象編譯成結(jié)構(gòu)體
有如下代碼:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
Person *per = [[Person alloc] init];
NSLog(@"per:%@",per);
}
return 0;
}
我們可以通過clang命名把.m文件編譯成.cpp文件,進(jìn)而可以清楚的看到為什么說oc對(duì)象底層是結(jié)構(gòu)體實(shí)現(xiàn)。
//clang命令
clang -rewrite-objc main.m [-o 別名]
編譯后如下:
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
2.結(jié)構(gòu)體內(nèi)存占用分析
2.1 常用數(shù)據(jù)類型內(nèi)存占用大小
我們都知道裕膀,在不同位數(shù)的編譯器環(huán)境下,數(shù)據(jù)類型不同其占用字節(jié)大小也不相同勇哗,區(qū)別如下:
-
32位編譯器
32位編譯器下.png
-
64位編譯器
64位編譯器下.png
2.2 結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
為了方便cpu更加快速地讀取存放在內(nèi)存中的數(shù)據(jù)昼扛,內(nèi)存在存放數(shù)據(jù)時(shí)會(huì)按照一定的規(guī)則來排列,規(guī)則如下:
- 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員欲诺,第
?個(gè)數(shù)據(jù)成員放在offset為0的地?抄谐,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員??或者成員的?成員??(只要該成員有?成員,?如說是數(shù)組瞧栗,結(jié)構(gòu)體等)的整數(shù)倍開始(?如int為4字節(jié),則要從4的整數(shù)倍地址開始存
儲(chǔ)斯稳。 - 結(jié)構(gòu)體作為成員:如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
其內(nèi)部最?元素??的整數(shù)倍地址開始存儲(chǔ).(struct a?存有struct b,b?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲(chǔ).) - 收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大成員的整數(shù)倍,不?的要補(bǔ)?。
2.3 內(nèi)存對(duì)齊計(jì)算
2.3.1 普通結(jié)構(gòu)體
例如有下面這樣一個(gè)結(jié)構(gòu)體迹恐,其內(nèi)存大小為24.分析如下:
struct AA{
double a; //[0,7]
char b; //[8]
int c; //[12,15]
int d; //[16,19]
};
printf("大小為:%d",sizeof(struct AA));//24
我們可以知道sizeof可以用來計(jì)算對(duì)象類型所占內(nèi)存大小挣惰,按照規(guī)則1我們可以將結(jié)構(gòu)體AA對(duì)象排列如下:
我們可以看到,a是第一個(gè)成員且長(zhǎng)度大小為8,所以占位序號(hào)為[0,7];
b的長(zhǎng)度大小為1憎茂,所以占位序號(hào)為[8];c的長(zhǎng)度大小為4珍语,但是根據(jù)規(guī)則一,這里c不能從序號(hào)9開始竖幔,因?yàn)?不滿足對(duì)齊數(shù)(4)的整數(shù)倍板乙,所以要從12開始排列;同理d的占位序號(hào)為[16,19],那么整個(gè)結(jié)構(gòu)體大小為19+1=20字節(jié)拳氢,又因?yàn)?0不滿足規(guī)則三募逞,所以總大小應(yīng)該是最大長(zhǎng)度(8)的最小整數(shù)倍且不小于20字節(jié)的數(shù),即24.
2.3.2 嵌套結(jié)構(gòu)體
例如結(jié)構(gòu)體嵌套的情況:
struct BB{
double a; //[0,7]
struct AA b; //[8,31]
char c; //[32]
};
printf("BB:%lu\n",sizeof(struct BB));//40
思路分析:
- a長(zhǎng)度為8且為首元素,所以占位序號(hào)為[0,7]
- b為結(jié)構(gòu)體變量且長(zhǎng)度大小為24馋评,根據(jù)規(guī)則二我們得出b不能從24開始放接,所以b的占位序號(hào)為[8,31]
- c的長(zhǎng)度為1,占位序號(hào)為[32],所以總大小為:32+1=33,然后33并不是結(jié)構(gòu)體BB的最大對(duì)齊數(shù)(8)的整數(shù)倍留特,因此BB大小為ceil(33/8.0)*8=40.
3.IOS中對(duì)象內(nèi)存大小
前面說完了結(jié)構(gòu)體內(nèi)存占用大小的情況纠脾,下面說說OC對(duì)象占用大小和實(shí)際申請(qǐng)大小該怎么計(jì)算吧。
首頁(yè)蜕青,我們往Person類中新增name,nickName,age等幾個(gè)屬性.
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,copy)NSString *nickName;
@property (nonatomic,assign)unsigned int age;
@property (nonatomic,assign)double score;
@end
并賦值如下:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
Person *per = [[Person alloc] init];
per.name = @"James";
per.nickName = @"Potter";
per.age = 18;
NSLog(@"per:%@",per);
NSLog(@"需要申請(qǐng)大小:%ld",class_getInstanceSize([per class]));//40
NSLog(@"實(shí)際申請(qǐng)大小:%ld",malloc_size((__bridge const void *)per));//48
}
return 0;
}
因?yàn)镻erson有四個(gè)屬性:name和nickName是指針變量各占用8個(gè)字節(jié)苟蹈,age占用4個(gè)字節(jié),score占用8個(gè)字節(jié)右核。其次通過結(jié)構(gòu)體我們可以前面clang編譯我們可以看到慧脱,結(jié)構(gòu)體里面還有一個(gè)isa指針,所以Person類總大小為:8+8+8+4+8=36,對(duì)齊后應(yīng)該是最大長(zhǎng)度的整數(shù)倍,即為40.
然而為什么在實(shí)際申請(qǐng)內(nèi)存過程中是48呢贺喝?其實(shí)蘋果底層在申請(qǐng)內(nèi)存是是按照16字節(jié)來申請(qǐng)的.
通過objc4中class_getInstanceSize源碼分析
/**
* Returns the size of instances of a class.
*
* @param cls A class object.
*
* @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
*/
OBJC_EXPORT size_t
class_getInstanceSize(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
//x+7 & (~7) --> 8字節(jié)對(duì)齊
return (x + WORD_MASK) & ~WORD_MASK;
}
//其中 WORD_MASK 為
# define WORD_MASK 7UL
實(shí)際申請(qǐng)內(nèi)存時(shí)instanceSize源碼分析
size_t instanceSize(size_t extraBytes) const {
//編譯器快速計(jì)算內(nèi)存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
// 計(jì)算類中所有屬性的大小 + 額外的字節(jié)數(shù)0
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//如果size 小于 16磷瘤,最小取16
if (size < 16) size = 16;
return size;
}
所以為什么蘋果要按照16字節(jié)對(duì)齊呢?
- 通常內(nèi)存是由一個(gè)個(gè)字節(jié)組成的搜变,cpu在存取數(shù)據(jù)時(shí)采缚,并不是以字節(jié)為單位存儲(chǔ),而是以塊為單位存取挠他,塊的大小為內(nèi)存存取力度扳抽。頻繁存取字節(jié)未對(duì)齊的數(shù)據(jù),會(huì)極大降低cpu的性能殖侵,所以可以通過減少存取次數(shù)來降低cpu的開銷贸呢。
- 由于在一個(gè)對(duì)象中,第一個(gè)屬性isa占8字節(jié)拢军,當(dāng)然一個(gè)對(duì)象肯定還有其他屬性楞陷,當(dāng)無屬性時(shí),會(huì)預(yù)留8字節(jié)茉唉,即16字節(jié)對(duì)齊固蛾,如果不預(yù)留结执,相當(dāng)于這個(gè)對(duì)象的isa和其他對(duì)象的isa緊挨著,容易造成訪問混亂艾凯。
- 16字節(jié)對(duì)齊后献幔,可以加快CPU讀取速度,同時(shí)使訪問更安全趾诗,不會(huì)產(chǎn)生訪問混亂的情況
以上就是我對(duì)對(duì)象內(nèi)存大小占用情況的簡(jiǎn)單分析蜡感,歡迎各位大佬們點(diǎn)贊。