結(jié)構(gòu)體內(nèi)存對(duì)齊的三大原則
1、數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)成員存儲(chǔ)的起始位置要從該成員大小或者成員的子成員大小的整數(shù)倍開(kāi)始存儲(chǔ)。
2、結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ)。
3、收尾工作:結(jié)構(gòu)體的總大小,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊。
結(jié)合例子做具體分析:
struct ELStruct1{
double a; //8 [0,7]
char b; //1 [8]
int c; //4 9...11 [12,15]
short d; //2 [16,17] //取最大內(nèi)部成員8的整數(shù)倍8*3=24對(duì)齊
}struct1;
struct ELStruct2{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15] //取最大內(nèi)部成員8的整數(shù)倍8*2=16對(duì)齊
}struct2;
打印結(jié)果:
struct1--24
struct2--16
我們?cè)賮?lái)一個(gè)稍微復(fù)雜一點(diǎn)的分析
struct ELStruct4{//接上繼續(xù)分析
double a; //8 20...23 [24,31]
char b; //1 [32]
int c; //4 33...35 [36,39]
short d; //2 [40,41] //取最大內(nèi)部成員8的整數(shù)倍8*6=48對(duì)齊
}struct4;
struct ELStruct1{
double a; //8 [0,7]
char b; //1 [8]
int c; //4 9...11 [12,15]
short d; //2 [16,17] //取最大內(nèi)部成員8的整數(shù)倍8*3=24對(duì)齊
}struct1;
struct ELStruct2{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15] //取最大內(nèi)部成員8的整數(shù)倍8*2=16對(duì)齊
}struct2;
struct ELStruct3{
double a; //8 [0,7]
int b; //4 [8,11]
char c; //1 [12]
short d; //2 13 [14,15]
int e; //4 [16,19]
struct ELStruct4 str;
}struct3;
打印結(jié)果:
struct1--24
struct2--16
struct3--48
我們繼續(xù)探究ELPerson
@interface ELPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *nickName;
@property(nonatomic,assign)long age;
@property(nonatomic,assign)float height;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//NSLog(@"Hello, World!");
ELPerson *per = [ELPerson alloc];
per.name = @"Eli";
per.nickName = @"Eli";
per.age = 18;
per.height = 180.6;
NSLog(@"%@ - %lu - %lu - %lu",per,sizeof(per),class_getInstanceSize([ELPerson class]),malloc_size((__bridge const void*)(per)));
}
return 0;
}
打印結(jié)果如下:
2021-06-09 15:31:08.107634+0800 KCObjcBuild[70838:2643794]
<ELPerson: 0x10068a320> - 8 - 40 - 48
sizeof()探究
我們先看看為什么sizeof(per)打印8
per是一個(gè)對(duì)象,對(duì)象的本質(zhì)是一個(gè)指針地址,指針地址的大小就是8字節(jié)
class_getInstanceSize()探究
繼續(xù)分析class_getInstanceSize([ELPerson class])
ELPerson 有4個(gè)屬性分別是NSString,NSString,long,float
分別是8+8+8+4=28字節(jié),再加上isa指針8字節(jié)就是36字節(jié)
我們跟蹤一下看看class_getInstanceSize()內(nèi)部實(shí)現(xiàn),看看為什么打印出來(lái)40和我們分析的不一樣
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
繼續(xù)跟蹤
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
8字節(jié)對(duì)齊
# define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
看到這里我們就明白了,他在內(nèi)部進(jìn)行了8字節(jié)的內(nèi)存對(duì)齊算法,所以把36字節(jié)對(duì)齊為40字節(jié)返回給我們了。我們以word_align(3)為例,下面具體分析下算法
3 <-> 0000 0011 7 <-> 0000 0111
3+7 <-> 0000 0011 ~7 <-> 1111 1000
+ 0000 1111 & 0000 1010
10 <-> 0000 1010 8 <-> 0000 1000
結(jié)果就是 word_align(3)=8跟常見(jiàn)的算法 (3 + 7) >> 3 << 3是一個(gè)效果,都是8字節(jié)對(duì)齊,要想16字節(jié)對(duì)齊就是(3 + 15) >> 4 << 4
malloc_size()打印原因探究
我們直接進(jìn)入源碼,繪制出流程圖如下所示

我們把segregated_size_to_fit()展開(kāi)分析一下
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
#define SHIFT_NANO_QUANTUM 4
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
主要代碼翻譯一下:
( size + 16 - 1 )>> 4 << 4
可以看到這個(gè)里面也是進(jìn)行了一個(gè)內(nèi)存對(duì)齊的操作, 并且是16位內(nèi)存對(duì)齊,和我們上面說(shuō)的方法一模一樣的。對(duì)40進(jìn)行16位對(duì)齊,結(jié)果就是48了
總結(jié):
對(duì)象內(nèi)部的成員變量,進(jìn)行8字節(jié)內(nèi)存對(duì)齊。對(duì)象本身是16字節(jié)對(duì)齊。