iOS底层原理 - 内存对齐&& malloc理解

1.回顾之前

​ 前面我们讲过alloc的一些底层探索中,在分配内存的时候有涉及到内存对齐的概念。instanceSize()中alignedInstanceSize()内存分配粗略的讲到了内存对齐的概念,下面我们来详细了解一下

2.内存对齐-初探

​ 什么是内存对齐?是否有很多问号,刚接触到这个概念的时候,也是很疑惑。概念:编译器在读取内存地址的时候,会按照一定的偏移量去读取;比如在一个写的struct里面定义一定变量,里面变量大小都是4字节,8字节,16 字节的,而且sizeof()大小也不是里面定义大小一样(大于里面定义的字节大小)。

​ 为什么呢?下面👇我们来说明一下原因

内存对齐的原因

​ 1.读取效率:cpu数据访问效率。一般平台系统会从基数位地址开始读取,那么需要两个周期才能拼凑成32bit/64bit。所以从效率上考虑系统会吧基数字节自动补齐成偶数位一个周期便可以读取完成,提高了读取效率

​ 2.硬件原因:一部分平台不能访问任意地址上的数据的,需要访问特定指定类型的数据,不然会出现异常(具体哪些平台尚不了解,需从网上了解...)

内存对齐规则

1)数据成员对其规则:结构体(struct)或联合体(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如数组、结构体等)的整数倍开始。

​ 总结:int为4字节,则要从4的整数倍开始存储

2)结构体作为成员:如果一个结构体内部包含其他结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。

总结:struct x中包含char ,int,double,float等元素,那应该从8(double字节大小)的整数倍开始存储

3)收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的需要补齐。在malloc中总是为16的倍数。

内存对齐-实战

举个栗子1

struct StructOne {
 char a;         // 1字节
 double b;       // 8字节
 int c;          // 4字节
 short d;        // 2字节
} Struct1;
​
struct StructTwo {
 double b;       // 8字节
 int c;          // 4字节
 char a;         // 1字节
 short d;        // 2字节
} Struct2;
​
struct StructOThree {
 double b;       // 8字节
 char a;         // 1字节
 int c;          // 4字节
 short d;        // 2字节
} Struct3;
​
NSLog(@"%lu---%lu---%lu",sizeof(Struct1),sizeof(Struct2),sizeof(Struct3));

输出结果为: 24—16—24

我们从内存对齐原则看,上面三个结构体在内存栈中的分布应该是这样的:


截屏2020-05-11 下午4.05.57.png

搜狐公众号推送的一片文章-内存布局(推荐阅读,很详细)

类对象内存开辟

1.准备工作

LLDB调试知识:

①: x/4gx 对象表示输出4个16进制的8字节地址空间(x表示16进制,4表示4个,g表示8字节为单位,等同于x/4xg 对象

②:pop:p表示"expression"——打印对象指针;而po是"expression -O"——打印对象本身

③:Xcode查看内存地址 debug->Debug Workflow->view memory

举个栗子🌰

@interface WXPerson : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) long height;
@property (nonatomic, assign) char c1;
@property (nonatomic, assign) float m_float;
@end
​
#import "WXPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
​
int main(int argc, const char * argv[]) {
 @autoreleasepool {
 // insert code here...
 WXPerson *p = [[WXPerson alloc] init];
 p.name = @"Kaemi";  //  NSString  8
 p.age = 18;         //  int       4
 p.height = 188;     //  long      8
 p.c1 =  'a';        //  char      1
 p.m_float =  12.6;   //  float     4
 NSLog(@"sizeof:%lu---申请内存大小为:%lu——-系统开辟内存大小为:%lu",sizeof([p class]),class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
 }
 return 0;
}

输出结果为:sizeof:8---申请内存大小为:40——-系统开辟内存大小为:48

几个知识点:sizeof() class_getInstanceSize() malloc_size() 是什么

sizeof:它是一个运算符,在编译时就可以获取类型所占内存的大小

class_getInstanceSize:依赖于<objc/runtime.h>,返回创建一个实例对象所需内存大小,不考虑malloc函数的话,内存对齐一般是以 8 字节对齐

malloc_size:依赖于<malloc/malloc.h>,返回系统实际分配的内存大小,在Mac、iOS中的malloc函数分配的内存大小总是 16 的倍数

那为啥class_getInstanceSize返回的值是40,malloc_size返回时48呢?下面👇我们来分析一下

3.malloc源码分析

内存开辟,我们在alloc 内存开辟中还遗留了一个问题:obj = (id)calloc(1, size) ,之前我们用objc源码无法断点下手,现在我们可以用libmalloc源码来分析一下

cooci malloc分析(很详细)

libmalloc源码中新建target,按照objc源码中的方式调用

void *p = calloc(1, 40);

断点malloc_zone_calloc();


malloc_1.png

通过调试台看一下具体实现

(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $0 = 0x000000010031cd14 (.dylib`default_zone_calloc at malloc.c:249)

确定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);
}

继续用lldb查看:

(lldb) p zone->calloc
(void *(*)(_malloc_zone_t *, size_t, size_t)) $0 = 0x000000010031e33f (.dylib`nano_calloc at nano_malloc.c:878)
malloc_2.png

再进去


malloc_3.png

最后我们会到这边

/*
 * #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; 
 }
 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;
}

class_getInstanceSize 是对象对8字节对齐,根据内存对齐原则可知分配了40大小;

malloc_size中的48是怎么来的。这里有多个size_t类,断点调试看了下的size是我们传进来的40,而slot_bytes刚好是我们的目标48,

在经过 (40 + 16 - 1) >> 4 << 4 操作后,结果为48,也就是16的整数倍——即16字节对齐

malloc_size 系统对对象做了16字节对齐

总结

对象的属性是8字节对齐

对象是16字节对齐

  • 因为内存是连续的,通过 16 字节对齐规避风险和容错,防止访问溢出

  • 同时,也提高了寻址访问效率,也就是空间换时间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345