我们知道任何一个程序在运行的时候实际是运行在内存中的,这个内存也就是我们通常所说的主存,也叫运行内存,也叫RAM(Random Access Memory),是可以直接与CPU进行交换数据的内部存储器。内存读取速度很快,所以作为操作系统运行程序的区域。不同的分区保存不同的值,值可以为指针,可以为对象,可以为二进制代码,可以为数字等,每个分区有自己的功能,它们一起协作为系统提供更好的任务划分。
堆区(heap)
由程序员管理(分配释放),堆区是由开发者“手动管理”或者程序结束时由系统全部回收,是一种树状的数据结构,一般用于存储由malloc、new等方式创建的对象。在iOS开发中,大多数关于内存管理方面的问题也多出自此:多是一些开发者没有及时回收内存,或者内存溢出以及泄漏等问题。
栈区(stack)
由编译器管理(分配释放),存放函数参数值、局部变量的值(函数中的基本数据类型)栈区的操作方式类似于数据结构中的栈(先进后出)。
全局区(静态区)
由编译器管理(分配释放),程序结束后由系统释放。存放全局变量和静态变量。有两块区域组成全局区(静态区),一块是存放未初始化的全局变量和静态变量,另一块是初始化完成的全局变量和静态变量,这两块区域是相邻的。
文字常量区
由编译器管理(分配释放),主要存储基本数据类型的值,以及常量,同样是进程结束后由系统回收。
程序代码区
存放函数的二进制代码,如果需要执行就加载到该区域中。
特例
这里应该再注意以下几个特例。
1.字符串类型
多个直接声明的相同字符串在内存中只占用一份内存,例如:
NSLog(@"hello1: %p", @"Hello");
NSLog(@"hello2: %p", @"Hello");
打印出来的结果是:
hello1: 0x100001168
hello2: 0x100001168
这个变量的地址是在常量区中存储,虽然声明的是两个字符串,看似应该开辟两端内存,但通过打印可以看出实际上是同一块内存,这是可以理解的,因为这是同一个固定的字符串,在编译期就确定了的,不会更改,是一个不可变量,因此引用同一份内存并没有什么问题,如果需要在此字符串上进行修改也是另外开辟一段内存。
其实上面的字符串就是__NSCFConstantString
,这种字符串存在于常量区,通常的不太短的字符串是__NSCFString
,存在于堆区。还有一种是将内容存于地址,叫做标签指针。详情可看:NSString的三种类型管理方式
- block类型
block声明的时候是在栈中的,但赋值给变量的时候会复制到堆中。
//声明一个block
NSLog(@"block in stack: %p", ^(){});
//将一个block赋值给一个变量
id block = ^(){};
NSLog(@"block in heap: %p", block);
示例代码:
打印结果:
a=3, *p=3
a=3, *p=1
这是关于block作用的一个非常典型的例子,可以很明确地看到block对外部变量的作用。两个方法中,都声明了一个int类型的a,赋值为1,接着声明一个int指针p指向a。在第一个方法中,直接调用了一个block,由于block还没有被赋值,所以这时block还没有复制到堆中,所以对于a来说也没有发生复制,与p指向的为同一内容。所以经过两次加1后,两者都为3;而对于第二个方法,将这个block赋值给一个临时变量,此时根据之前我们所说的内容,发生了复制,__block修饰的a也复制为一份新的内容,但p依然指向之前的内容,此时p的指向和a已经不是同一内容了,所以∗p依然为1,而a经过两次加1后,变为3。