1.对象的内存地址分布
首先创建一个Person类,其中包含了name
、nickname
、age
、c1
和c2
五个属性。此时创建一个person对象,并对其属性进行赋值,然后打印person对象的内存地址。
通常在调试窗口使用
p person
命令打印对象person的内存地址。使用x/4gx
或x/8gx
+ 内存地址
或$11
,(其中$11
为图中person 类的标识符 )可以来打印具体的地址下的内存地址情况,如图。
- 在使用
x/8gx
打印对象内存地址的结果中,冒号前面的是从对象的首地址,冒号后是其属性的值,如果属性未被赋值时,为0x0000000000000000
; - 标识
1
的0x0000600002d6fd20
地址是指对象person
的首地址; - 标识
2
的是当前person对象的isa
指针,指向的是类; - 标识
3
包含了age
,c1
和c2
三个属性的值,在OC中为了节约内存空间将基本数据类型的数据可以放在一条数据内存中 - 开辟内存时进行了内存重排,从图中
0x0000001200006261
在0x000000010e0c6038
和0x000000010e0c6058
前的原因
2.结构体的内存对齐
通过查看源码可以看出,OC对象的内存对齐来自于结构体的内存,所以研究结构体。结构体指针的大小为8字节,结构体的内存大小取决于其内部属性。
struct Struct1 {
double a; // 8 0 1 2 3 4 5 6 7
char b; // 1 8
int c; // 4 12 13 14 15
short d; // 2 16 17 总计需要:17 总计开辟:24
} struct1;
struct Struct2 {
double a; // 8 0 1 2 3 4 5 6 7
int b; // 4 8 9 10 11
char c; // 1 12
short d; // 2 14 15 总计需要:15 总计开辟:16
struct Struct1 struct1;
} struct2;
打印如下:
2020-10-02 11:52:16.814642+0800 内存对齐[2209:74659] 24-40
首先分析结构体struct1
:
在64位架构下,按照定义计数从0位开始
-
double a
为8
个字节则为(0,1,2,3,4,5,6,7)8
位 -
char b
为1
个字节,根据连续性原则b
会从第8
位开始,所以b
占用第8
位; -
int c
为4
个字节,根据内存对齐的原则,当前属性的开始位是其所占内存大小的整数倍
,而b
占用了第8
位,第9
位不是4
的倍数,所以需要向后移动直到第12
位,占用了(12,13,14,15)
位 -
short d
为2
个字节,根据当前属性的开始位是其所占内存大小的整数倍
原则,d
的占用了(16,17)
位 -
struct1
实际占用内存17
字节,根据结构体开辟内存大小是其属性最大值的整数倍
,并且要满足大于或等于实际需要大小,因此struct1
的内存大小为24
。
对于结构体struct2
来说:
-
double a
为8
个字节则为(0,1,2,3,4,5,6,7)8
位 -
int b
为4
个字节,根据内存对齐的原则,当前属性的开始位是其所占内存大小的整数倍
,所以b
占用了(8,9,10,11)
位 -
char c
为1
个字节,所以占用了第12
位 -
short d
为2
个字节,根据当前属性的开始位是其所占内存大小的整数倍
原则,d
占用了(14,15)位 - 由上面的
struct1
得到struct1
的内存大小为24
字节,根据如果结构体中有结构体作为属性时,该结构体的起始位需要是该结构体中最大属性值得整数倍
,此时struct1
正好从16
开始,并占用了24
字节 - 由上可得出
struct2
的内存大小为40
3. instanceSize方法分析
instanceSize
方法是在初始化alloc
方法中的方法_class_createInstanceFromZone
中调用的,用来计算对象开辟的内存大小。首先通过宏定义得fastPath()
来判断是否有较大概率进行快速路径,此时使用的是gcc
中定义的__builtin_except(N, 1)
表示较大概率执行,此时返回调用cache
中的方法fastInstanceSize
来计算内存大小,在fastInstanceSize
中会走计算size_t size = _flags & FAST_CACHE_ALLOC_MASK
通过按位与
的计算方式将_flags
与宏定义的FAST_CACHE_ALLOC_MASK 0x1ff8
计算得出内存大小,然后调用align16
方法,在align16
方法中使用(x+size_t(15) & ~size_t(15))
取16位以下为0的方式,进行了按照16位的倍数的内存对齐
-
_flags
是chass_rw_t
结构体中的属性,在初始化alloc
方法时系统回调用objc_class
结构体中的setInfo
方法,并调用data->setFlags
方法为_flags
进行了赋值 -
x+size_t(15) & ~size_t(15)
: 将x与15相加,然后再和15按位取反
的数进行按位与
计算,示例如下, 令 x = 7:
x = 7 0000 0111
15 0000 1111
x + 15 0001 0110
~15 1111 0000
x + size_t(15) & ~size_t(15)
16 0001 0000
可以看出该算法是开辟的内存使用实际开辟的内存进行16的倍数扩容。
4. calloc 分析
calloc调用流程.png- 在
malloc_zone_calloc
中返回的是void *
类型的指针ptr
,所以在该方法中重点为ptr
的赋值部分ptr = zone->calloc(zone, num_items, size)
,在该方法中进入calloc
又会产生递归,通过研究此处实际调用default_zone_calloc
方法。 - 在
default_zone_calloc
中return zone->calloc(zone, num_items, size)
此时又调用了一次zone
的calloc
方法,在实际当中调用的则为nano_malloc
方法 - 在
nano_malloc
方法中
static void *nano_malloc(nanozone_t *nanozone, size_t size)
{
if (size <= NANO_MAX_SIZE) {
void *p = _nano_malloc_check_clear(nanozone, size, 0);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->malloc(zone, size);
}
重点是调用了_nano_malloc_check_clear
方法进行对p
指针进行赋值,在初始化时size 为 0,一定是小于NANO_MAX_SIZE
= 256的,所以此处一定会返回p
。
- 在
_nano_malloc_check_clear
方法中,重点在如下语句中
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
mag_index_t mag_index = nano_mag_index(nanozone);
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
所以重点在一个slot_bytes
的计算,然后对 ptr
的赋值,而slot_bytes
的计算是重点,接下来研究segregated_size_to_fit
方法
static MALLOC_INLINE size_t
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;
}
在代码中 NANO_REGIME_QUANTA_SIZE
为 (1 << SHIFT_NANO_QUANTUM)
也就是16
,SHIFT_NANO_QUANTUM
为 4
,所以在初始化时,通过size = NANO_REGIME_QUANTA_SIZE
给 size
赋值为16
, (size + NANO_REGIME_QUANTA_SIZE -1) >> SHIFT_NANO_QUANTUM
是将低四位进行抹零,slot_bytes = k << SHIFT_NAMO_QUANTUM
将k进行左移四位,通过这两步将高位保留低位抹零,从而保证为16
的倍数。
网友评论