前言
Objective-C从名字来看就可以知道这是一门超C语言,所以了解C语言的内存模型对于理解Objective-C的内存管理、性能优化有很大的帮助。
C语言内存模型图如下:
从图中可以看出内存被分成了5个区,每个区存储的内容都不相同。
1、栈区(stack)
传入函数的参数值、函数体内声明的局部变量等,由编译器自动分配释放,通常在函数执行结束后就释放了。
(注意:不包括static修饰的变量,static意味该变量存放在全局/静态区)
其操作方式类似数据结构中的栈,先进后出。
栈内存分配运算内置于处理器的指令集,效率很高,但是分配的内存容量有限,比如iOS中栈区的大小是2M(看网上说,也有人说1M,我也不知道具体大小,但栈区的内存肯定不会太大)。
我们无法也不需要管理栈区的内存分配。
可以根据下面的代码可以理解:
int main ()
{
int array[] = {1,2,3,4,5};
------------------|
if ( i > 0 ) { |
... |
... |
} |
foo(array); |
|
{ |
int k = 9; --- |
array[1] = k; |->k的生存域 |->array的生存域
... --- |
} |->执行到这一步k被释放|
} |
|
void foo(int * const number) { |
int j = 0; |
--- |
... | |
number[2] = j; |->j的生存域 |
... | |
-------------------
} |->执行到这一步j、array被释放
当代码中当程序执行到变量生存域(作用域)之外 的时候,变量生存域(作用域)已经结束了,编译器就会自动释放掉变量所占的内存,所以理解好生存域(作用域)就理解了栈区的内存分配。
2、堆区(heap)
堆区的内存是由代码分配和释放,用于存放进程运行中被动态分配的内存段,堆区的大小并不固定,可动态扩张或缩减。
变量通过new、alloc、malloc、realloc分配的内存块就存放在堆区。
堆区的内存都是动态分配的。
当进程调用alloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)。
当利用realse释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
如果应用程序没有释放掉,操作系统会自动回收,分配方式类似于链表。
因为现在iOS基本都使用ARC来管理对象,所以也不需要手动释放。
一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
3、全局/静态区
全局/静态区是存放全局变量和静态变量的。
已初始化的全局变量和静态变量存放在一块区域。
未初始化的全局变量和静态变量在相邻的另一块区域。
由static修饰的变量会成为静态变量,该变量的内存由全局/静态区在编译阶段完成分配,且仅分配一次。
static可以修饰局部变量也可以修饰全局变量。
全局/静态区的内存在编译阶段完成分配,程序运行时会一直存在内存中,只有当程序结束后才会由操作系统释放。
4、常量区
常量区是一块比较特殊的存储区,常量区里面存放的是常量,常量字符串就存放在常量区。
常量区的内存在编译阶段完成分配,程序运行时会一直存在内存中,只有当程序结束后才会由操作系统释放。
5、代码区
代码区是用来存放可执行文件的操作指令(存放函数的二进制代码),其实就是存放程序的所有代码。代码区需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
下面的代码可以更清晰的理解:
#import "ViewController.h"
NSInteger age;//age存放在<未初始化的全局静态区>
NSInteger score = 100;//age存放在<已初始化的全局静态区>
//@"SunSatan"存放在<常量区>,name存放在<已初始化的全局静态区>
NSString *name = @"SunSatan";
static NSString *titleView = @"SunSatan";//titleView存放在<已初始化的全局静态区>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int flag;//flag存放在<栈区>
NSString *number = @"8090";//number存放在<栈区>,@"8090"存放在<常量区>
//array_1、array_2存放在<堆区>
NSArray *array_1 = [[NSArray alloc]init];
NSArray *array_2 = [NSArray array];
NSArray *array_3 = @[];//array_3存放在<栈区>
NSInteger total = [self getTotalNumber:1 number2:1];//total存放在<栈区>
}
- (NSInteger)getTotalNumber:(NSInteger)number1 number2:(NSInteger)number2{
return number1 + number2;//number1、number2存放在<栈区>
}
@end
总结
在编译阶段代码区、常量区、全局/静态区就已经分配完成并且大小固定,所以指向这些区的指针不会产生崩溃性的错误。
而栈区和堆区内存分配随着程序运行而变化(堆的创建销毁,栈的弹入弹出)。
在iOS中,堆区的内存是应用程序共享的,堆区的内存分配是系统负责的。