有兴趣的朋友可以观看我的上一篇文章《OC对象原理探究之alloc探索》
一、对象的内存空间
先给HPerson添加对象:

打上断点,运行,然后在lldb中输入x p,显示对象p的内存分布:

0x108f079c0是对象p的内存首地址,接下来就是对象p的内存。
iOS端为小端模式,所有需要倒着读取:

内存打印出来是什么呢?
是isa!
为什么没有打印出isa呢?
因为要&ISA_MASK:
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# endif
复制代码
这里使用的是模拟器,所以&0x0000000ffffffff8:

现在就正确的打印出isa了!
后面的0则为对象的属性的存储空间!
进入debug查看一下内存:

输入内存首地址:

发现即使没有给属性赋值,依旧会开辟内存!
给属性赋值后再运行:

x/5gx为格式化输出!
x/nuf <addr>
n表示要显示的内存单元的个数
—————
u表示一个地址单元的长度:
b表示单字节
h表示双字节
w表示四字节
g表示八字节
—————
f表示显示方式,可取如下值:
x按十六进制格式显示变量
d按十进制格式显示变量
u按十进制格式显示无符号整型
o按八进制格式显示变量
t按二进制格式显示变量
a按十六进制格式显示变量
i指令地址格式
c按字符格式显示变量
f按浮点数格式显示变量
复制代码
如果把height改为BOOL类型:

就会发现age和height放在了一起!
这是苹果的底层对内存进行了优化,即内存对齐!
二、对象的内存影响因素
先引入runtime:
#import <objc/runtime.h>
复制代码
然后打印对象实例大小:

发现大小为32。
我们再删除一些属性,重新打印:
![图片[1]-OC对象原理探究之内存对齐-一一网](https://www.proyy.com/skycj/data/images/2021-06-10/16627b0e8d99d76e1289a058ce7a74e1.jpg)

发现大小为16!
说明属性对类的大小是有影响的!
加上成员变量后再打印:
![图片[2]-OC对象原理探究之内存对齐-一一网](https://www.proyy.com/skycj/data/images/2021-06-10/acad690866e7d6c522aa6c630675fdb7.jpg)

发现大小为24。
说明成员变量对类的大小也是有影响的!
那么方法是否会影响类的大小呢?
添加方法:
![图片[3]-OC对象原理探究之内存对齐-一一网](https://www.proyy.com/skycj/data/images/2021-06-10/0b5dfd49ca316fee1bc7fcf58c162cb5.jpg)
![图片[4]-OC对象原理探究之内存对齐-一一网](https://www.proyy.com/skycj/data/images/2021-06-10/2f4a8eb9b34ce00d069d0c60d455bdcd.jpg)

发现类的大小依旧是24!
说明方法对类的大小是没有影响的!
结论:只有成员变量会影响类的大小!
如下图所示:

三、结构体内存对齐
1、各类型所用字节:

2、内存对⻬的原则:
1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,
结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存
储。
min(当前开始的位置m n) m = 9 n = 4
9 10 11 12
2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b
⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
成员的整数倍.不⾜的要补⻬。
复制代码
3、分析结构体:
先看看这2个结构体:
结构体1:
struct LGStruct1 {
double a;
char b;
int c;
short d;
}struct1;
复制代码
结构体2:
struct LGStruct2 {
double a;
int b;
char c;
short d;
}struct2;
复制代码
这2个结构体占用的字节是否一样呢?
我们来分析一下:
结构体1:
double为8字节,从8的整数倍开始,占用8字节,所以a在[0-7]
char为1字节,从1的整数倍开始,占用1字节,所以b在[8]
int为4字节,从4的整数倍开始,占用4字节,所以9、10、11空出,c在[12-15]
short为2字节,从2的整数倍开始,占用2字节,所以d在[16-17]
复制代码
所用结构体1一共18字节。
但是根据内存对齐原则,结构体的总⼤⼩为内部最⼤成员的整数倍,即24!
结构体2:
double为8字节,从8的整数倍开始,占用8字节,所以a在[0-7]
int为4字节,从4的整数倍开始,占用4字节,所以b在[8-11]
char为1字节,从1的整数倍开始,占用1字节,所以c在[12]
short为2字节,从2的整数倍开始,占用2字节,所以13空出,d在[14-15]
复制代码
所用结构体2一共16字节。
根据内存对齐原则,总大小为16!
再来看看结构体3:
struct LGStruct3 {
double a;
int b;
char c;
short d;
int e;
struct LGStruct1 str;
}struct3;
复制代码
这种结构体嵌套的结构体又该怎么分析呢?
double为8字节,从8的整数倍开始,占用8字节,所以a在[0-7]
int为4字节,从4的整数倍开始,占用4字节,所以b在[8-11]
char为1字节,从1的整数倍开始,占用1字节,所以c在[12]
short为2字节,从2的整数倍开始,占用2字节,所以13空出,d在[14-15]
int为4字节,从4的整数倍开始,占用4字节,所以e在[16-19]
LGStruct1为24字节,LGStruct1内最大成员为8字节,所以从8的整数倍开始,占24字节,所以str在[24-47]
复制代码
所用结构体3一共48字节。
根据内存对齐原则,总大小为48!
我们看看打印的结果吧:

和我们分析的一致!
那么我们平时写属性的时候需要注意顺序吗?
来看看对象的内存分布就知道了:
![图片[5]-OC对象原理探究之内存对齐-一一网](https://www.proyy.com/skycj/data/images/2021-06-10/47688e26c70b0bc7f3ab0703b3abf3e1.jpg)

发现内存分布并没有按照属性顺序排序!
xcode已经帮我们做了优化了!
也可以进行二进制类型的重排、数据类型的重排等等。
4、为什么需要内存对齐?
一个结构体有char、int、long类型:
如果没有内存对齐:

处理器读取数据需要先读1字节,再读4字节,再读8自己,多次读取,效率低下!
如果有内存对齐:

处理器读取数据只需要8字节、8字节的去读就可以了!方便读取!效率非常高!
所以内存对齐就是空间换时间!提高效率!
四、malloc源码引入
依旧是Hperson:

引入malloc.h
#import <malloc/malloc.h>
复制代码
看看这个打印结果:

3个值分别是什么呢?
sizeof 是否是 8 + 8 + 4 + 8 = 28 ?还是 32 ?又或者是40呢?
class_getInstanceSize 呢?
malloc_size 呢?
复制代码
来看看打印结果:

分别为8、40、48!
为什么呢?
p为指针,指针的大小为8!
class_getInstanceSize([HPerson class])获取的是HPerson类的大小!
HPerson类有isa、name、nickName、age、height,共5个成员变量!所以大小为40!
复制代码
那么malloc_size又是什么呢?
我们点进去看看:

发现没有实现!
再看看路径:

去苹果的源代码目录下载源码:

然后可以参考《libmalloc源码浅谈》进行编译!
五、malloc分析探索思路
在上一篇文章《OC对象原理探究之alloc探索》中我们已经知道了calloc是开辟内存的方法。
现在我们就来探索一下calloc!
先在源码的main.h中使用calloc方法:

跟进去看看:
void *
calloc(size_t num_items, size_t size)
{
return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}
复制代码
再进入到_malloc_zone_calloc方法:
MALLOC_NOINLINE
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
malloc_zone_options_t mzo)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (os_unlikely(malloc_logger)) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
if (os_unlikely(ptr == NULL)) {
malloc_set_errno_fast(mzo, ENOMEM);
}
return ptr;
}
复制代码
那么这段代码的重点在哪呢?
先看return,再根据return寻找:

跟着calloc进去:

发现只是一个声明!
接下来怎么探索呢?
全局搜索calloc:

发现有赋值,即有存储值,那么就可以打印!
断上断点进行打印:

全局搜索找到default_zone_calloc进去:
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
zone = runtime_default_zone();
return zone->calloc(zone, num_items, size);
}
复制代码
继续断点打印:

全局搜索找到nano_calloc进去:
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;
if (calloc_get_size(num_items, size, 0, &total_bytes)) {
return NULL;
}
if (total_bytes <= NANO_MAX_SIZE) {
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
复制代码
那么,nano_calloc方法的重点又在哪呢?
先看return!
第一个return NULL;返回空,这种肯定有问题,可以忽略!
那么return p;和return zone->calloc(zone, 1, total_bytes);会走哪个呢?
在return p;前有个if (total_bytes <= NANO_MAX_SIZE)可得出,正常情况肯定是走return p;!
然后进入创建p的_nano_malloc_check_clear方法:
static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);
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));
if (ptr) {
unsigned debug_flags = nanozone->debug_flags;
#if NANO_FREE_DEQUEUE_DILIGENCE
size_t gotSize;
nano_blk_addr_t p; // the compiler holds this in a register
p.addr = (uint64_t)ptr; // Begin the dissection of ptr
if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
malloc_zone_error(debug_flags, true,
"Invalid signature for pointer %p dequeued from free list\n",
ptr);
}
if (mag_index != p.fields.nano_mag_index) {
malloc_zone_error(debug_flags, true,
"Mismatched magazine for pointer %p dequeued from free list\n",
ptr);
}
gotSize = _nano_vet_and_size_of_free(nanozone, ptr);
if (0 == gotSize) {
malloc_zone_error(debug_flags, true,
"Invalid pointer %p dequeued from free list\n", ptr);
}
if (gotSize != slot_bytes) {
malloc_zone_error(debug_flags, true,
"Mismatched size for pointer %p dequeued from free list\n",
ptr);
}
if (!_nano_block_has_canary_value(nanozone, ptr)) {
malloc_zone_error(debug_flags, true,
"Heap corruption detected, free list canary is damaged for %p\n"
"*** Incorrect guard value: %lu\n", ptr,
((chained_block_t)ptr)->double_free_guard);
}
#if defined(DEBUG)
void *next = (void *)(((chained_block_t)ptr)->next);
if (next) {
p.addr = (uint64_t)next; // Begin the dissection of next
if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
malloc_zone_error(debug_flags, true,
"Invalid next signature for pointer %p dequeued from free "
"list, next = %p\n", ptr, "next");
}
if (mag_index != p.fields.nano_mag_index) {
malloc_zone_error(debug_flags, true,
"Mismatched next magazine for pointer %p dequeued from "
"free list, next = %p\n", ptr, next);
}
gotSize = _nano_vet_and_size_of_free(nanozone, next);
if (0 == gotSize) {
malloc_zone_error(debug_flags, true,
"Invalid next for pointer %p dequeued from free list, "
"next = %p\n", ptr, next);
}
if (gotSize != slot_bytes) {
malloc_zone_error(debug_flags, true,
"Mismatched next size for pointer %p dequeued from free "
"list, next = %p\n", ptr, next);
}
}
#endif /* DEBUG */
#endif /* NANO_FREE_DEQUEUE_DILIGENCE */
((chained_block_t)ptr)->double_free_guard = 0;
((chained_block_t)ptr)->next = NULL; // clear out next pointer to protect free list
} else {
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
}
if (cleared_requested && ptr) {
memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
}
return ptr;
}
复制代码
这种很长的代码应该怎么解读呢?
就从小玩到大!
把if折叠起来看:

这就很清楚了:存在ptr就会怎么怎么样,不存在ptr又会怎么怎么样!
先看看存在ptr会做什么!
一看就发现if里面都是调用malloc_zone_error方法,很明显,这个方法是报错的方法!
再看看不存在ptr会做什么!
进入segregated_next_block方法:
static MALLOC_INLINE void *
segregated_next_block(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
while (1) {
uintptr_t theLimit = pMeta->slot_limit_addr; // Capture the slot limit that bounds slot_bump_addr right now
uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr));
b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation.
if (b < theLimit) { // Did we stay within the bound of the present slot allocation?
return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go
} else {
if (pMeta->slot_exhausted) { // exhausted all the bands availble for this slot?
pMeta->slot_bump_addr = theLimit;
return 0; // We're toast
} else {
// One thread will grow the heap, others will see its been grown and retry allocation
_malloc_lock_lock(&nanozone->band_resupply_lock[mag_index]);
// re-check state now that we've taken the lock
if (pMeta->slot_exhausted) {
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
return 0; // Toast
} else if (b < pMeta->slot_limit_addr) {
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
continue; // ... the slot was successfully grown by first-taker (not us). Now try again.
} else if (segregated_band_grow(nanozone, pMeta, slot_bytes, mag_index)) {
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
continue; // ... the slot has been successfully grown by us. Now try again.
} else {
pMeta->slot_exhausted = TRUE;
pMeta->slot_bump_addr = theLimit;
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
return 0;
}
}
}
}
}
复制代码
发现是一个死循环!
里面有一个注释:
// Did we stay within the bound of the present slot allocation?
即可知segregated_next_block方法是在寻找可用内存空间!
但是我们的重点是在找这个对象的大小!
那么是哪个决定了大小呢?
在上一个方法_nano_malloc_check_clear方法里面:
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
复制代码
很明显slot_bytes就是大小!即:
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
复制代码
于是我们进入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:
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
#define NANO_QUANTA_MASK (NANO_REGIME_QUANTA_SIZE - 1)
#define NANO_SIZE_CLASSES (NANO_MAX_SIZE/NANO_REGIME_QUANTA_SIZE)
复制代码
得到了确定大小的公式:
k = size + 15
然后k先右移4位再左移4位
即向上取16的整数倍!
复制代码
这就是内存对齐的公式!即16字节对齐!
和上一章的字节对齐公式类似!
字节对齐即为向上取8的整数倍,也就是先右移3位再左移3位!
所以:
对象的内存对齐是16字节对齐!
成员变量的对齐是8字节对齐!
复制代码
六、对象内存对齐原理
对象的内存对齐为什么是16字节呢?
比如有3个对象的大小都为8 * 3 = 24字节!
如果内存对齐是8字节的话,这3个对象就会紧密相连!
容易造成访问错误!
如果内存对齐是16字节的话,就会有空余,提高容错率!
同时,对象来自于NSObject,而NSObject自身有一个isa成员变量,所以再加任意一个成员变量的对象的大小至少都是16字节!
所以对象的内存对齐是按照16字节对齐!
























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)