OC与C的内存管理及其交互

开胃菜

首先我们从最基本的C中三种链接属性,分别是:外部(external)、内部(internal)、无(none)。我们可以通过关键字extern、static来修改变量的链接属性。extern关键将一个变量声明为外部的链接属性之后,便可以去访问其他文件中同名该变量。static关键字在用于代码块外部的变量时是将其设置为内部链接属性,如果是在代码块内部则将该变量声明为静态变量。具有块作用域、函数作用域或函数原型作用域的变量都是无链接属性变量。
  然后再来看看C中变量的存储类型。存储类型决定了变量的创建、销毁时机。存储变量的位置一共三个地方:普通内存、运行时堆栈、硬件寄存器。结合C中的三种链接属性,具体可以分为:

  • 栈区:代码块中的变量在一般情况下为自动变量(由高地址向低地址生长)
  • 堆区:由malloc、realloc、calloc等函数动态生成的变量。这些变量我们只能访问其地址,而且当我们不再使用之后需要收到去free掉(由低地址向高地址生长)。
  • 全局区/静态区:代码块之外声明的变量总是存储于静态内存中(默认的链接属性为external),这一般分配在__data段中。未初始化的变量放在一起,已经初始化的紧挨地放着,而这些未经初始化的是存放在__bss段中,__bss段只是为未初始化的全局变量和局部静态变量预留位置而已,要等到程序装载到内存中时才会具体的去分配空间。
    由于函数实参总是在堆栈中进行传递,所以函数的形参不能设置为static。
  • 常量区:常量字符串。它们一般存在于__rodata段中(只读数据段)。
  • 代码区:一般在编译阶段就已经决定了其在虚拟内存中的位置,主要是分配在__text/__code段。

在代码块内部声明的变量的缺省存储类型是自动的,即它存储于栈中,称为自动变量
如果代码块被多次执行,那么自动变量将会重复创建,每一次创建时,它们在内存中的位置可能会不同。

自动变量只能显示地初始化它,如果没有初始化那么该变量的值将是上一次该地址中的值。
至于上面提到的寄存器中的变量,因为CPU对于寄存器的读取速度非常快,通常编译器会将使用频率很高的变量将其移到寄存器中。如果寄存器变量在多线程编程时出现了问题,我们可能需要显式将该变量声明为volatile,让编译器不对该变量进行优化。

/**
 全局静态区
 */
int a = 10;  /// external
extern int b;/// external
static int c;/// internal
int d(int e){/// 函数d 默认为external
    int f = 15;/// auto 栈区
    static int g = 20;/// 静态变量 静态区
    return 0;
}

static int h(int i){/// 函数h 修改为static,internal
    register int j;/// 寄存器类型,但是不一定起作用
    int *k = malloc(sizeof(int));/// 堆区
    free(k);
    const int m = 25;/// 常量区
    return 1;
}

C中各个类型变量的内存管理

C语言中的内存管理与链接属性和所在内存区域都有直接关系。栈区的自动变量会在其作用域之后自动进行销毁;堆区的中由用户动态的创建的内存,需要手动调用free函数来释放(否则会造成内存泄漏); 全局区/静态区中的变量由系统创建和销毁,它们在程序开始运行之前就创建好,静态区的变量在程序运行过程中我们不能去修改; 常量区程序结束后由系统释放。

关于堆的一点儿说明:
如果我们在使用mallocfree时是无序的话,最终会产生堆碎片。
而且被分配的内存是经过对齐的,一般为2的次方。

堆的末端由一个称为break的指针来标识,当堆管理器需要更多内存时,它可以通过系统调用brksbrk来移动break指针。


OC与C的交互(__bridge)


当oc在和c相关的函数(CoreFoundataion、Runtime)进行交互时,我们需要将OC的类型传递到C中,也需要将C中的数据返回给OC使用。这其中就需要使用它们类型之间的转换。在id类型或者对象变量赋值给void *或者逆向赋值时都需要进行特定的转换,单纯的赋值我们可以使用 __bridge

NSObject *obj = [[NSObject alloc] init];
void *p_obj = (__bridge void *)(obj);
NSObject * r_obj = (__bridge NSObject *)(p_obj);

相对于__bridge,我们可以使用__bridge_retained修饰符,它即可以进行转换,也能持有被转换的对象(上例中的obj),因此该对象不会被废弃。其语法形式如下:

__bridge_retained <#CF type#>)<#expression#>

__bridge中还有个__bridge_transfer,它的作用和__bridge_retained相反,被转换的变量(上例中的p_obj)所持有的对象(上例中的obj)会在r_obj被赋值之后释放掉,其语法形式如下:

__bridge_transfer <#Objective-C type#>)<#expression#>

把上诉例子进行修改:

NSObject *obj = [[NSObject alloc] init];
void *p_obj = (__bridge_retained void *)(obj);
NSObject * r_obj = (__bridge_transfer NSObject *)(p_obj);

当我们在C语言的结构中,需要使用OC的类型作为结构成员,除了将OC的类型转换为void *之外,我们可以使用__unsafe_unretained修饰符(这个修饰符会在后面介绍)。

/// 在C中使用OC的对象方式
typedef struct rls_temp_ctx{
    NSObject __unsafe_unretained *obj;
    void *target;
} rls_temp_context;
/// 在C中传入OC对象
rls_temp_context tmp_ctxs = {
    .obj = [NSObject new],
    .target = (__bridge void *)(self)
};

但是在使用obj时,由于__unsafe_unretained存在悬浮指针的问题,必须要判断该值是否存在。


OC内存管理

前面看了C的内存管理,还看了C和OC的交互,最后就来看看在OC中内存管理应该注意的事项。
  现在我们讨论OC的内存管理是基于ARC的,其中对象变量的创建和释放问题和C的内存管理有点儿相似。大多数情况下系统会帮我们进行内存管理,我们只需要明确自己所声明的对象或者变量存在于什么区域(上面提到的内存区域),给它们添加合适的修饰符等等。
  大部分情况下,对于栈区、堆区、全局静态区的变量对象和C是相同的,我们可以类比来分析OC中对象或者变量的创建和释放时机。ARC中栈区用autoreleasepool管理的变量和C中的自动变量的内存管理时机很相似。
  在OC中使用基于C的函数时,通过malloc等函数声明的变量,都需要我们明确地调用free函数进行释放!抑或在使用CoreFondation、Runtime时,基本上如果遇到了包含有Copy, Create等关键字函数,在使用完成之后都需要手动释放内存。


2017-09-13更新:
当我们使用Runtime时,运用下面的方法来动态创建一个对象时,被创建的对象不会被释放,但是对应的release方法又是MRC时代的。所以我们可以使用如下方法来解决:

/// 创建对象
id obj = ((id(*)(id,SEL))objc_msgSend)(((id(*)(id,SEL))objc_msgSend)([self class],@selector(alloc)),@selector(init));
...
...
...
/// 释放对象
((id(*)(id,SEL))objc_msgSend)(obj,NSSelectorFromString(@"release"));

内存管理关键字

下面来介绍一下,在Objective-C的ARC中所涉及到的关键字。

  • 1、__strong为默认值,在声明成员变量和方法参数时也可以使用!
__strong id obj_var = [[NSObject alloc] init];

作用:默认的行为。

  • 2、__weak是不会持有对象实例,__weak修饰符可以避免循环引用
__weak id obj2 = nil;
{
        __strong id obj_var = [[NSObject alloc] init];/// 自己生成对象并持有
        obj2 = obj_var;/// obj2持有对象的弱引用
        NSLog(@"__weak %@",obj2);/// 此时由于在obj_var变量可用域中,obj2此时有值
}
NSLog(@"__weak %@",obj2);/// 由于不在obj_var作用域之外,obj_var被释放。而且obj2是弱引用于obj_var的,所以此时obj2值为空

作用:避免循环引用,不持有对象实例

  • 3、__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。它和__weak类似,不会持有对象实例;
__unsafe_unretained id obj1 = nil;
{
/// 在obj_var作用域内,__unsafe_unretained和__weak是一样的
        __strong id obj_var = [[NSObject alloc] init];
        obj1 = obj_var;
        NSLog(@"__unsafe_unretained %@",obj1);
}
NSLog(@"__unsafe_unretained %@",obj1);/// 此时变量已经被遗弃,成为悬浮指针

在使用__unsafe_unretained修饰符时,赋值给__strong修饰符的变量时,需要检查被赋值的对象是否存在(也就是被__unsafe_unretained修饰的变量)

作用:在iOS4之前__weak的替代品,但是在将其赋值给其他时,最好做非空判断

  • 4、__autoreleasing修饰符的变量替代调用MRC时代的autorelease方法,该对象会被注册到autoreleasepool中。以下是__autoreleasing修饰符的使用场景:
    1)、在生成对象时,编译器会检查方法名是否是以alloc/new/copy/mutablcopy开始(自己生成自由持有)。如果不是自己生成的则自动将返回值注册到autoreleasepool中。
    2)、对象作为返回值时,编译器会自动将其注册到autoreleasing中。
    3)、在使用__weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。
    4)、id的指针或者对象的指针(NSObject **/NSError **)在没有显示指定时会被附加上__autoreleasing修饰符
NSError *error = nil;
BOOL result = [self performOperationWithError:&error];

最后还是去看看这套题,它的解释对于理解内存的释放很有益处。对于这套题我已经推荐了几次了,哈哈哈。

相关引用

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

推荐阅读更多精彩内容

  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 4,943评论 1 17
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,019评论 29 471
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,465评论 0 1
  • 晨读感悟《穷查理宝典》0614 昨晚太晚睡了,我先打个盹。
    周萍丶雷雨里的大少爷阅读 306评论 9 10
  • 关系:书店(主要盈利方以及供货商) 中介(提供客源以及信誉担保) 购书人(主要是学生以及教育...
    周星星777阅读 644评论 0 0