Objective-C 对象存储在堆上而不是栈上 why?

Objective-C 中对象默认存储在堆上,而非栈上,主要是因为栈对象生命周期固定、空间有限,而堆对象更灵活且便于内存管理。虽然栈对象创建速度快,但其缺点如生命周期固定和内存限制导致其不适用于大部分对象。Objective-C 的 block 是一种特殊的栈对象,需要通过 copy 操作才能在其他地方持有。堆对象通过引用计数管理,而栈对象由系统自动回收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是栈对象和堆对象

在Objective-C 中,对象通常是指一块有特定布局的连续内存区域。我们通常这样创建一个对象:

NSObject *obj = [[NSObject alloc] init]; 
这行代码创建了一个 NSObject 类型的指针 obj 和一个 NSObject 类型的对象,obj 指针存储在栈上,而其指向的对象则存储在堆上(简称为堆对象)。

目前 Objective-C 并不支持直接在栈上创建对象(简称为堆对象),但可以通过如下方式间接地创建:

struct { 
Class isa; 
} fakeNSObject; 
fakeNSObject.isa = [NSObject class];

NSObject obj = (NSObject )&fakeNSObject; 
NSLog(@”%@”, [obj description]);

栈对象 obj 也能正常工作,由此可见栈对象和堆对象都是可行的,但为什么 Objective-C 不默认使用栈对象呢?

二、栈对象优缺点

1、优点

速度

在栈上创建对象是非常快的,因为很多东西在编译时就确定了,运行时分配空间几乎不耗时;相对而言在堆上创建对象就非常耗时。

简单

栈对象的生命周期是确定的,对象出栈以后就会被释放,不会存在内存泄漏,但这同时也是栈对象的最大缺点。

2、缺点

生命周期固定

Objective-C 变量有效范围是由 “{}” 包含的块来决定的,也就是说栈对象的生命周期仅限于其所在的块里,出了块立马会被释放。一个对象被创建以后有可能会通过方法调用传递到别的方法,当栈对象的创建方法返回时,栈对象会被一起 pop 出栈而释放,导致其没法在别处被继续持有。此时 retain 操作会失效,除非用 copy 方法在想持有该栈对象的地方重新拷贝一份属于自己的栈对象。

因此,栈对象回给对象的内存管理造成相当大的麻烦。

空间

现代操作系统的栈和线程绑定,而栈空间是有限的,具体如下:

512 KB (secondary threads) 
8 MB (OS X main thread) 
1 MB (iOS main thread) 
因此对象如果都在栈上创建不太现实,而堆只要物理内存不告警可以无限制使用。

综合以上优缺点,Objective-C 选择用堆存储对象。

三、真的没有栈对象吗

实际上 Objective-C 里的 block 却是栈对象,因此栈对象面临的问题在 block 身上一个都不少,但由于 block 是仅有的特殊对象,大家对它的特殊都已经习惯了,比如入行第一年的时候老师就告诉我们想持有一个 block 要用 copy 将 block 从栈拷贝到堆上。

另外,根据前面所说,栈对象的有效区域仅限于其所在的块,因此像下面的代码就无法输出期望的结果:

void (^block)();
if(x)
{
block = ^{ printf("x\n"); };
}
else
{
block = ^{ printf("not x\n"); };
}
block();

这也是大家需要特别注意的地方。

四、参考文档

Threading Programming Guide

Stack and Heap Objects in Objective-C

五、其他

为了访问你创建在heap 中的数据,你最少要求有一个保存在stack 中的指针,因为你的CPU 通过stack 中的指针访问heap 中的数据。

你可以认为stack 中的一个指针仅仅是一个整型变量,保存了heap 中特定内存地址的数据。实际上,它有一点点复杂,但这是它的基本结构。

简而言之,操作系统使用stack 段中的指针值访问heap 段中的对象。如果stack 对象的指针没有了,则heap 中的对象就不能访问。这也是内存泄露的原因。

在iOS 操作系统的stack 段和heap 段中,你都可以创建数据对象。

stack 对象的优点主要有两点,一是创建速度快,二是管理简单,它有严格的生命周期。stack 对象的缺点是它不灵活。创建时长度是多大就一直是多大,创建时是哪个函数创建的,它的owner 就一直是它。不像heap 对象那样有多个owner ,其实多个owner 等同于引用计数。只有heap 对象才是采用“引用计数”方法管理它。

stack 对象的创建

只要栈的剩余空间大于stack 对象申请创建的空间,操作系统就会为程序提供这段内存空间,否则将报异常提示栈溢出。

heap 对象的创建

操作系统对于内存heap 段是采用链表进行管理的。操作系统有一个记录空闲内存地址的链表,当收到程序的申请时,会遍历链表,寻找第一个空间大于所申请的heap 节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。 

例如:NSString 的对象就是stack 中的对象,NSMutableString 的对象就是heap 中的对象。

引入堆和栈的概念

  • 所以问题就来了,为什么OC对象需要进行内存管理,而其它非对象类型比如基本数据类型就不需要进行内存管理呢?
  • 只有OC对象才需要进行内存管理的本质原因?

因为:Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,就是release
OC对象存放于堆里面(堆内存要程序员手动回收)
非OC对象一般放在栈里面(栈内存会被系统自动回收)
堆里面的内存是动态分配的,所以也就需要程序员手动的去添加内存、回收内存 

总结区别

  • 按管理方式分
    • 对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
    • 对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
  • 按分配方式分
    • 堆是动态分配和回收内存的,没有静态分配的堆
    • 栈有两种分配方式:静态分配和动态分配
      • 静态分配是系统编译器完成的,比如局部变量的分配
      • 动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,它的动态分配也由系统编译器进行释放,不需要程序员手动管理 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值