iOS底層探索--Struct內(nèi)存對齊

知識點須知

C和OC各數(shù)據(jù)類型在32位和64位CPU中的占用的字節(jié)大小列表:

C OC 32位 64位
bool BOOL(64位) 1 1
signed char (_ _signed char)int8_t、BOOL(32位) 1 1
unsigned char Boolean 1 1
short int16_t 2 2
unsigned short unichar 2 2
int int32_t NSInterger(32位)、boolean_t(32位) 4 4
unsigned int boolean_t(64位)、NSUInteger(32位) 4 4
long NSInteger(64位) 4 8
unsigned long NSUInteger(64位) 4 8
long long int64_t 8 8
float CGFloat(32位) 4 4
double CGFloat(64位) 8 8

對象的內(nèi)存大小與屬性重排

  1. class_getInstanceSize
    ??首先我們來看一下Runtime函數(shù)class_getInstanceSize獲取實例的大小。
    其底層源碼是:
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

類的屬性大小,采用8字節(jié)對齊原則

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

#   define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

發(fā)現(xiàn)取決于data()->ro->instanceSize,即是對象bits在運行時的class_rw_tro,只讀內(nèi)存的實例大小instanceSize,其實在編譯期就定了,在運行時,class_rw_t的存在(因為其實可讀可寫的)就決定可以動態(tài)的添加方法協(xié)議,動態(tài)關(guān)聯(lián)屬性。

// May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

小結(jié):從源碼可以知道,對象的大小與屬性有關(guān)

  1. 驗證對象大小與屬性相關(guān)

??新建一個類,沒有任何屬性,看看創(chuàng)建出來的對象大小是多少

@interface PSYPerson : NSObject
///**  */
//@property (nonatomic,copy) NSString *name;
///**  */
//@property (nonatomic,assign) NSInteger age;
///**  */
//@property (nonatomic,copy) NSString *nickName;

@end

....

 PSYPerson *psy1 = [[PSYPerson alloc] init];
        
 PSYPerson *psy2 = [[PSYPerson alloc] init];
        
 NSLog(@"psy1對象大?。?lu",class_getInstanceSize(psy1.class)); 
 NSLog(@"psy2對象大?。?lu",class_getInstanceSize(psy2.class));

輸出結(jié)果:


從結(jié)果可以看到,為什么是8呢,明明沒有任何屬性?這里首先說明,對象默認(rèn)繼承自obj_object結(jié)構(gòu)體,默認(rèn)繼承了一個isa指針占8個字節(jié)。
給類添加屬性@property (nonatomic,copy) NSString *name;,重新運行程序,查看打印結(jié)果:

添加name屬性后

與我們預(yù)想的一樣,為了進一步驗證,添加如下屬性(跑在6s真機上測試,根據(jù)上面知識點須知 C和OC不同數(shù)據(jù)類型在32位和64位CPU中占有字節(jié)大小列表):

@interface PSYPerson : NSObject

/** 8字節(jié) */
@property (nonatomic,copy) NSString *name;
/** 4字節(jié) */
@property (nonatomic,assign) int age;
/** 1字節(jié)  */
@property (nonatomic) char msg;
/** 1字節(jié)  */
@property (nonatomic) BOOL isWorking;
@end

分析:8字節(jié)+4字節(jié)+1字節(jié)+1字節(jié) = 14字節(jié)
再加上isa指針8字節(jié) :14字節(jié)+8字節(jié) = 22字節(jié),根據(jù)8字節(jié)對齊原則應(yīng)該是24字節(jié)

源碼說只與類的ivars有關(guān),那方法對其是否有影響呢?筆者有添加了一個類方法一個對象方法,繼續(xù)輸出印證,發(fā)現(xiàn)還是24字節(jié);


方法對對象大小無影響
  1. 屬性重排

對象的屬性在內(nèi)存中是怎樣存在的呢?
因為屬性是有32字節(jié),筆者特意打印了8段數(shù)據(jù),實際對象的地址是從0x2807e05c0開始,占32個字節(jié),也就是下圖中紅框起來的數(shù)據(jù)區(qū),可以知道,在對象被創(chuàng)建出來的時候?qū)傩员荒J(rèn)賦值為 0 或則 nil ,其他的是臟數(shù)據(jù)。


賦值之后,重新打印查看內(nèi)存數(shù)據(jù):
image.png

可發(fā)現(xiàn)不管屬性順序是怎樣,先賦值哪一個,運行的時候,蘋果會對對象的屬性進行重排,使其內(nèi)存優(yōu)化。

總結(jié)
  1. 對象屬性內(nèi)存采用8字節(jié)對齊
  2. 對象大小與ivars有關(guān),與方法無關(guān)
  3. 蘋果會進行屬性重排,以優(yōu)化內(nèi)存

結(jié)構(gòu)體內(nèi)存對齊

??上面研究了對象內(nèi)存對齊,對于結(jié)構(gòu)體是什么樣的呢?研究結(jié)構(gòu)體其實是有很大的意義,因為在底層探索過程中,基本上90%以上都是結(jié)構(gòu)體共用體。

struct PSYStruct1 {
    double a;       // 8   
    char b;         // 1    
    int c;          // 4    
    short d;        // 2 
}struct1;

struct PSYStruct2 {
    double a;       // 8 
    int b;          // 4    
    char c;         // 1   
    short d;        // 2  
}struct2;

估計很大一部分人認(rèn)為,這兩個結(jié)構(gòu)體一抹抹一樣樣,大小都是16字節(jié),可是運行結(jié)果卻讓人大跌眼鏡:

image.png
結(jié)構(gòu)體內(nèi)存對齊
  1. 數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)體(struct)或聯(lián)合體/共用體(union)的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0??的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結(jié)構(gòu)體)的整數(shù)倍開始

    • eg:比如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲
  2. 結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲。

    • eg: struct a里存有struct b,b里有char ,int , double 等元素,那b應(yīng)該從8的整數(shù)倍開始存儲)
  3. 規(guī)則三:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補齊。


根據(jù)數(shù)據(jù)成員對齊規(guī)則:

\color{#0x0000}{struct1}

double a; ? // 8 從0開始存8位 也就是 [0 , 7]
char b; ? ?// 1 [8]
int c; ? ? // 4 (9, 10, 11, [12, 13, 14, 15] 從4的整數(shù)倍開始存,也就是12開始
short d; ? // 2 [16, 17] 17個字節(jié),但是根據(jù)規(guī)則三,大小為最大成員8的整數(shù)倍取24字節(jié)

同理

\color{#0x0000}{struct2}

double a; // 8 從0開始存8位 也就是 [0 , 7]
int b; ? // 4 [8, 9, 10, 11] 因為8是4的整數(shù)倍,因此從8開始存
char c; ? // 1 [12]
short d; ? // 2 [14, 15] 根據(jù)規(guī)則三,大小為最大成員8的整數(shù)倍所以總共16個字節(jié)


結(jié)構(gòu)體嵌套情況

struct PSYStruct3 {
    double a;
    int b;
    char c;
    short d;
    int e;
    struct PSYStruct1 str;
}struct3;

\color{#0x0000}{struct2}

double a; ? // 8字節(jié) [0 , 7]
int b; ? ? // 4字節(jié) [8, 9, 10, 11]
char c; ? ? // 1字節(jié) [12]
short d; ? ? // 2字節(jié) (13, [14, 15]
int e; ? ? ? // 4字節(jié) [16, 17, 18, 19]
struct PSYStruct1 str;// (20, 21, 22, 23, [24, 47]

根據(jù)規(guī)則結(jié)構(gòu)體作為成員:,str結(jié)構(gòu)體開始存儲的位置是str結(jié)構(gòu)體內(nèi)部最大元素的大小8的整數(shù)倍24開始,占24個字節(jié),就是到47

所以整個結(jié)構(gòu)體占48字節(jié)。


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容