iOS-底层原理-内存对齐
1.iOS中获取内存大小的三种方式
1.获取内存大小的三种方式分别是:
1.1 sizeof
1.2 class_getInstanceSize
1.3 malloc_size
后两者需要添加对应的头文件 #import <objc/runtime.h> #import <malloc/malloc.h>
2.各自的含义是什么
2.1 sizeof
sizeof
是一个操作符
,不是函数
我们一般用sizeof计算内存大小时,传入的对象主要是数据类型
,这个是在编译阶段就会确定大小而不是运行时
sizeof
最终得到的结果是该数据类型占用空间的大小
2.2 class_getInstanceSize
class_getInstanceSize
是 runtime
提供的api,用于获取类的实例对象所占用的内存大小
,并返回具体的字节数,其本质就是获取实例对象中成员变量的内存大小
,也就是对象真正需要的内存大小
, 和里面的属性和成员变量相关
2.3 malloc_size
这个函数是获取系统实际分配的内存大小
3.下面通过自定义的类LGPerson来测试一下各自的内存输出大小以及怎么来理解
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@end
4.打印输出三种获取内存大小的方式
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
LGPerson *person = [LGPerson alloc];
person.name = @"DaShen";
person.nickName = @"KC";
NSLog(@"person == %@",person);
NSLog(@"sizeof == %lu", sizeof(person));
NSLog(@"class_getInstanceSize == %lu", class_getInstanceSize([LGPerson class]));
NSLog(@"malloc_size == %lu", malloc_size((__bridge const void *)(person)));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
4.1三种获取内存大小的输出,如下图所示
1.sizeof 输出长度是 8
2.class_getInstanceSize 输出长度是 40
3.malloc_size 输出长度是 48
4.iOS系统是16字节对齐,所以,真实需要的40个字节,16字节对齐、16的倍数,开辟48字节
解释如下
sizeof(person) 因为person是指针,所以,占用的内存大小是8字节
class_getInstanceSize([LGPerson class]) 对象真正需要的内存大小
isa 指针 8字节 [0 - 7]
name 8字节 [8 - 15]
nickName 8字节 [16 - 23]
age 4字节 [24 - 27]
height 8字节 [32 - 39]
说明,顺序不一定是这样的,但是原理是这样的
malloc_size 系统开辟的内存空间大小,16字节对齐方式,所以对象真实内存大小是40,系统开辟16的整数倍是48
4.2 LGPerson类新增一个int weight的属性
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@property (nonatomic, assign) long height;
@end
可以看出输出结果完全一致,
1.sizeof 输出长度是 8
2.class_getInstanceSize 输出长度是 40
3.malloc_size 输出长度是 48
4.iOS系统是16字节对齐,所以,真实需要的40个字节,16字节对齐、16的倍数,开辟48字节
isa 指针 8字节 [0 - 7]
name 8字节 [8 - 15]
nickName 8字节 [16 - 23]
age 4字节 [24 - 27]
weight 4字节 [28 - 31]
height 8字节 [32 - 39]
说明,顺序不一定是这样的,但是原理是这样的
4.3 LGPerson类再次新增一个NSString * home的成员变量
@interface LGPerson : NSObject
{
NSString * _home;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@property (nonatomic, assign) long height;
@end
输出结果如下
1.sizeof 输出长度是 8
2.class_getInstanceSize 输出长度是 48
3.malloc_size 输出长度是 48
4.iOS系统是16字节对齐,所以,需要的刚好是48字节和系统开辟刚好一致
isa 指针 8字节 [0 - 7]
name 8字节 [8 - 15]
nickName 8字节 [16 - 23]
age 4字节 [24 - 27]
weight 4字节 [28 - 31]
height 8字节 [32 - 39]
home 8字节 [40 - 47]
说明,顺序不一定是这样的,但是原理是这样的
4.4来看一下这些属性和成员变量的真实存储情况
1.上面得出来了,int类型的age和int类型的weight占用一个8个字节的存储空间
2.下面用 x/6gx person 打印6段8个字节的存储空间,内存地址所存储的具体内容见图片注释文字
截图
对象内部真实存储的值.jpeg
输出
2022-05-03 13:26:52.492652+0800 002-系统内存开辟[2692:188124] person == <LGPerson: 0x600001ab87b0>
2022-05-03 13:26:52.492853+0800 002-系统内存开辟[2692:188124] sizeof == 8
2022-05-03 13:26:52.492911+0800 002-系统内存开辟[2692:188124] class_getInstanceSize == 48
2022-05-03 13:26:52.492966+0800 002-系统内存开辟[2692:188124] malloc_size == 48
(lldb) x/6gx person
0x600001ab87b0: 0x01000001027115e9 0x000000010270c068
0x600001ab87c0: 0x0000009600000012 0x000000010270c028
0x600001ab87d0: 0x000000010270c048 0x00000000000000b9
(lldb) po 0x600001ab87b0
<LGPerson: 0x600001ab87b0>
(lldb) po 0x01000001027115e9
72057598373860841
(lldb) po 0x000000010270c068
北京
(lldb) po 0x000000010270c028
DaShen
(lldb) po 0x000000010270c048
KC
(lldb) po 0x00000000000000b9
185
(lldb) po 0x0000009600000012
644245094418
(lldb) po 0x96
150
(lldb) po 0x12
18
(lldb)
2.通过结构体来验证一下内存对齐的基本原则、基本原理
1.数据类型在32位和64位情况下占用的字节数
1. 可见只有long 和 unsigned long在32位下是4个字节,64位下是8个字节,其他的都是一致的
2.两个结构体数据类型和数量一致,只是数据类型的顺序不一样,结构体占用内存的长度不一致,如下图所示
两个结构体如下
struct LGStruct1 {
double a;
char b;
int c;
short d;
} struct1;
struct LGStruct2 {
double a;
int b;
char c;
short d;
} struct2;
1. struct1 数据类型:double, char, int, short
2. struct2 数据类型:double, int , char, short
3.可见两个结构体只是int和char数据类型交换了顺序,得出来的结构体内存长度是不一样的
,唯一的区别
只是在于定义变量的顺序不一致
,那为什么它们占用的内存大小不相等呢?其实这就是iOS中的内存字节对齐
现象
sizeof struct1 == 24
sizeof struct2 == 16
3.内存对齐原则
每个特定平台上的编译器都有字节的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack()
,n = 1, 2, 4, 8, 18来改变这一系数,其中的n就是你要指定的“对齐系数”
。在iOS中,Xcode默认为``#pragma pack(8)```,即8字节对齐
[原则一]
结构体(struct)或者联合体(union)的数据成员,第一个数据成员
放在offset为0
的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如 int 4字节,那么存储位置可以是0-4-8-12-16-20
依次类推)
数据成员的对齐规则可以理解为min(m, n)
的公式, 其中 m
表示当前成员的开始位置
, n
表示当前成员所需要的位数
。如果满足条件 m 整除 n
(即 m % n == 0
), n 从 m
位置开始存储, 反之继续检查m+1 能否整除 n
, 直到可以整除, 从而就确定了当前成员的开始位置
[原则二]
数据成员为结构体:如果一个结构体里有某些结构体成员,则该结构体成员
要从其内部最大元素大小的整数倍
地址开始存储,比如struct a里有struct b, b里面有char, int, double, short
,那么b应该从8的整数倍地址
开始存储,即最大成员为double 8字节
。如果只有char, int, short
,那么b从4的整数倍地址
开始存储,即最大成员为int 4字节
。
且这里的8和4视为b的自身长度
作为外部结构体的成员变量的内存大小
,即b这个结构体可以视作8或者4字节这样的结构体成员
,参与结构体总大小
必须是内部最大成员的整数倍
的计算
[原则三]
结构体内存的总大小
,必须是结构体中最大成员内存的整数倍
,不足的需要补齐
根据内存对齐原则分析一下上面的结构体内存情况
struct LGStruct1 {
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] ---> 存储位置
} struct1; // 0-17,总共18个字节,结构体整体内存大小必须是最大成员的整数倍,
// 最大成员是double 为8字节,也就是要从18个字节补齐到24个字节
struct LGStruct2 {
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] ---> 存储位置
} struct2; // 0-15,总共16个字节,结构体整体内存大小必须是最大成员的整数倍,
// 最大成员是double 为8字节,恰好是8的两倍不需要补齐
结论:结构体内存大小与结构体成员的内存大小顺序有关
4. 结构体嵌套
代码如下
struct LGStruct1 {
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] ---> 存储位置
} struct1;
// 0-17,总共18个字节,结构体整体内存大小必须是最大成员的整数倍,
// 最大成员是double 为8字节,也就是要从18个字节补齐到24个字节
struct LGStruct2 {
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] ---> 存储位置
} struct2;
// 0-15,总共16个字节,结构体整体内存大小必须是最大成员的整数倍,
// 最大成员是double 为8字节,恰好是8的两倍不需要补齐
struct LGStruct3 {
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] ---> 存储位置
struct LGStruct2 e; // 结构体作为成员变量,LGStruct2内部最大成员的整数倍地址开始存储,也就是double 8的整数
} struct3; // LGStruct2整体长度是16个字节,也就是从 [24-39]
// LGStruct2 a,8字节,[24 25 26 27 28 29 30 31]
// LGStruct2 b,4字节,[32 33 34 35]
// LGStruct2 c,1字节,[36]
// LGStruct2 d,2字节,[38 39]
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
NSLog(@"sizeof struct1 == %lu",sizeof(struct1));
NSLog(@"sizeof struct2 == %lu", sizeof(struct2));
NSLog(@"sizeof struct3 == %lu", sizeof(struct3));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
输出如下
1.这里的24 + 16 = 40仅仅是巧合
,变换LGStruct3结构体内部成员的顺序整体大小会发生变化
,遵循内存对齐原则
结论:无论是否嵌套
结构体的大小都与成员变量内存大小的顺序有关
2022-05-04 10:00:11.900852+0800 002-系统内存开辟[1784:83817] sizeof struct1 == 24
2022-05-04 10:00:11.901047+0800 002-系统内存开辟[1784:83817] sizeof struct2 == 16
2022-05-04 10:00:11.901096+0800 002-系统内存开辟[1784:83817] sizeof struct3 == 32
Xcode截图如下
结构体嵌套.jpeg
3.内存优化(属性重排)
1.通过可以看出,结构体的大小结构体成员内存大小的顺序
有关系
2.如果是结构体中数据成员
是根据内存从小到大
的顺序定义的,根据内存对齐规则来计算结构体内存大小,需要增加有较大的内存padding即内存占位符,才能满足内存对齐规则,比较浪费内存
3.如果是结构体中数据成员
是根据内存从大到小
的顺序定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding
即可满足堆存对齐规则,这种方式就是苹果中采用的
,利用空间换时间
,将类中的属性进行重排
,来达到优化内存
的目的
4.下面通过LGPerson自定义类里面属性的调整来看看具体的属性重排,其实与最开始看到的是一样的,这里动态调整属性会更直观
LGPerson类结构如下
@interface LGPerson : NSObject
{
char c3;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
- (void)setCharValue:(char)c;
@end
@implementation LGPerson
- (void)setCharValue:(char)c {
c3 = c;
}
@end
调用如下,创建对象,并对属性进行赋值
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
LGPerson *person = [LGPerson alloc];
person.name = @"DaShen";
person.nickName = @"KC";
person.age = 18;
person.height = 185;
person.c1 = 'a';
person.c2 = 'b';
[person setCharValue:'c'];
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
读取属性值如下
isa
0x01000001003ad611 ---> isa 存储值
单独占用8字节的属性
po 0x00000001003a8028 ---> DaShen ---> name指针8字节
po 0x00000001003a8048 ---> KC ---> nickName指针8字节
po 0x00000000000000b9 ---> 185 ---> height long 8字节
内存优化,属性重排
po 0x0000001200626163 ---> 77315858787
读取这个值时出现乱码,这里无法找到剩余属性和成员变量值的原因是苹果针对age、c1、c2、c3属性和成员变量的内存进行了重排
,因为age int类型占用4个字节,c1、c2、c3都是char类型,每个占用一个字节,所以形成4+1+1+1
的方式,按照8字节对齐,不足补齐的原则存储在一看内存中
age、c1、c2、c3的读取
a的ASCII码值是97
b的ASCII码值是98
c的ASCII码值是99
po 0x00000012 ---> 18 ---> age int 4字节
po 0x61 ---> 97 ---> c1 char 1字节
po 0x62 ---> 98 ---> c2 char 1字节
po 0x63 ---> 99 ---> c3 char 1字节
(lldb) x/6gx person
0x6000029507b0: 0x01000001003ad611 0x0000001200626163
0x6000029507c0: 0x00000001003a8028 0x00000001003a8048
0x6000029507d0: 0x00000000000000b9 0x0000000000000000
(lldb) po 0x00000001003a8028
DaShen
(lldb) po 0x00000001003a8048
KC
(lldb) po 0x00000000000000b9
185
(lldb) po 0x0000001200626163
77315858787
(lldb) po 0x12
18
(lldb) po 0x61
97
(lldb) po 0x62
98
(lldb) po 0x63
99
(lldb)
iOS内存优化,属性重排,截图说明,person这个实例对象的内存情况
和对应的值存储情况
总结
所以,这里可以总结下苹果中的内存对齐思想:
大部分的内存都是通过固定的内存块进行读取,
尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费
的,苹果会自动对属性进行重排,以此来优化内存
4.到底是8字节对齐,还是16字节对齐?
1.对于一个对象来说,8个字节就能满足需求了,但是,会出现一个对象紧挨着另一个对象的情况,中间毫无间隙,apple系统为了防止一切的容错,采用的是16字节对齐的内存
,主要是因为采用8字节对齐时,两个对象的内存会紧挨着,显得比较紧凑,而16字节比较宽松,利于苹果以后的扩展。
2.如果不全是16的整数倍
的话,中间会有8个字节的间隔,比如一个对象需要24个字节
,16字节内存对齐
的话,会给该对象开辟32个字节
的内存空间,后面的8个字节
没有任何数据,预留给这个对象的,防止可能的出错
3.iOS新版的alloc流程,2个两个函数都是16字节对齐的
1.instanceSize() ---> 计算需要开辟的内存空间大小 ---> 16字节对齐
2.calloc() ---> 开辟内存,内部最终内部调用的是16字节对齐 ---> 16字节对齐
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 1.计算需要开辟的内存空间大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2.向系统申请开辟内存,并返回指针
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
// 3.指针关联cls,设置isa指针
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
4.instanceSize()的调用顺序
instanceSize() ---> fastInstanceSize --->align16()
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
__builtin_expect
这个指令是 gcc
引入的,作用是允许程序员将最有可能执行的分支告诉编译器。
这个指令的写法为:__builtin_expect(EXP, N)
。意思是:EXP==N 的概率很大
所以fastpath
的含义是,为1
的概率大,slowpath
的含义是为 0
的概率大
fastpath(cache.hasFastInstanceSize(extraBytes))
是大概率执行的,也就是16字节对齐
instanceSize函数()
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
fastInstanceSize()函数
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
align16()函数
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
4.calloc()的调用顺序
需要在malloc源码里面查找
calloc() ---> malloc_zone_calloc() ---> default_zone_calloc() ---> runtime_default_zone() ---> nano_calloc() ---> _nano_malloc_check_clear()
segregated_size_to_fit ()
函数是计算16字节对齐的
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;
}
segregated_size_to_fit()函数具体实现
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
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;
}
核心代码解析
先左移4位,16以下全部抹零,再右移4位,刚好是16的倍数
k = (size + 15) >> 4
slot_bytes = k << 4
5.16字节对齐算法,两种方式,结果是一样的,下面二进制演示以下
align16()
segregated_size_to_fit()
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
k = (size + 15) >> 4
slot_bytes = k << 4
align16()函数,假设 x = 2
(x + 15) & ~(15)
0001 0001 ---> 2 + 15
1111 0000 ---> ~15
0001 0000 ---> 17 &(~15) == 16 ---> 这样本身2个字节就开辟了16个字节
segregated_size_to_fit()函数,假设 size = 2
k = (size + 15) >> 4
slot_bytes = k << 4
0001 0001 ---> 2 + 15 == 17
0000 0001 ---> 2 + 15 == 17 >> 4
右移4位,16以下全部抹零,相当于 k / 16
0001 0000 ---> 17 << 4
左移4位,扩大16倍,相当于k * 16
---> 这样本身2个字节就开辟了16个字节
6.calloc()实际开辟内存的大小,16字节对齐验证
申请8字节
, 开辟16字节
申请24字节
,开辟32字节
申请32字节
,开辟32字节
申请40字节
,开辟48字节
申请41字节
,开辟48字节
代码如下
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
void * p1 = calloc(1, 24);
NSLog(@"sizeof p1 == %lu", sizeof(p1));
NSLog(@"malloc_size p1 == %lu", malloc_size(p1)); //malloc_size()系统实际上开辟的内存大小
NSLog(@"\n\n");
void * p2 = calloc(1, 32);
NSLog(@"sizeof p2 == %lu", sizeof(p2));
NSLog(@"malloc_size p2 == %lu", malloc_size(p2));
NSLog(@"\n\n");
void * p3 = calloc(1, 40);
NSLog(@"sizeof p3 == %lu", sizeof(p3));
NSLog(@"malloc_size p3 == %lu", malloc_size(p3));
NSLog(@"\n\n");
void * p4 = calloc(1, 41);
NSLog(@"sizeof p4 == %lu", sizeof(p4));
NSLog(@"malloc_size p4 == %lu", malloc_size(p4));
NSLog(@"\n\n");
void * p5 = calloc(1, 8);
NSLog(@"sizeof p5 == %lu", sizeof(p5));
NSLog(@"malloc_size p5 == %lu", malloc_size(p5));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Xcode截图如下,calloc()16字节对齐
calloc 16字节对齐.jpeg
5.总结
1.iOS新版本采用16字节对齐的方式,计算需要开辟多少内存空间的函数和开辟内存空间的函数都是16字节对齐,双16字节对齐
2.结构体内存对齐三原则
3.iOS系统优化优化内存,进行了属性、成员变量等重排