【iOS】内存分区
内存分区
- 五大分区是什么?
- 栈区
- 堆区
- 静态区 全局区
- 常量区
- 代码区
- static、extern、const比较
- static
- extern全局变量
- const常量
五大分区是什么?
在编写代码变量都是怎么在内存中存取的呢?其实内存中分为五大分区,分别是栈区(系统管理的地方)、堆区(程序员控制的地方)、常量区(全局区)、静态区、代码区。这里我们还要知道内存指的就是RAM。
栈区
创建临时变量时由编译器自动分配,在不需要的时候自动消除变量的存储区。
主要用于存储函数的局部变量、函数参数和返回地址,遵循一个先进先出的原则。我们可以把栈看做一个临时寄存,交换的内存区。
用户栈在程序执行期间可以动态地扩展和收缩
优点:
- 栈是系统数据结构,对应的线程/进程是唯一的,栈中存储了进程所需要的所有局部变量、函数的参数等内容
- 栈的优点是快速高效,但是缺点是有限制,数据不灵活
- 栈空间分为静态分配和动态分配两种,静态分配由编译器完成,动态分配在运行时决定的。
- 为可移植的程序起见,栈的动态分配操作是不被鼓励的。
堆区
就是由alloc
、new
、malloc
、realloc
创建的对象所分配的内存块,他们的释放系统不会主动去管,需要我们开发者去告诉系统什么时候释放这块内存;若是程序猿不释放,程序结束后系统会自己释放。
堆是向高地址扩展的数据结构,是不连续的内存区域,程序员负责在何时释放内存(在ARC中通常会自动回收)。
优点:
- 灵活方便、数据适应面广泛,但是效率有一定降低
- 堆是函数库内部数据结构,不一定唯一
- 不同堆分配的内存无法相互操作
- 堆空间的分配总是动态的
- 虽然程序结束时所有的数据结构都会被释放回系统,但是精准的申请内存,释放内存匹配时良好的程序基本要素
这里我们以下面这个分配堆内存为例:
NSObject* obj = [NSObject new]
- 在堆内存中申请一块大小合适的空间
- 在这块内存空间里创建我们的对象
- 初始化对象的属性,为对象的属性赋上默认值,基本数据类型赋值0 —— C语言类型为NULL —— OC指针类型为nil
- 返回这个对象在堆空间的地址,将这个地址赋给
obj
,这样以后我们访问obj
变量时实际访问的就是堆空间中的NSObject
对象,但是obj变量因为本质上是一个指针变量,他是存储在栈空间的
静态区 全局区
全局区分为两个区域,用于存储全局变量以及静态变量
- .bss:存放未初始化的全局变量、静态变量
- .data:已初始化的全局变量、静态变量
常量区
存放常量字符串,程序结束后由系统释放,该区是编译时分配的内存空间,在程序运行过程中,这个内存中的数据一直存在,程序结束后由系统释放。
代码区
用来存放函数的二进制代码,代码段需要防止在运行时被非法修改,故而只允许读取操作,不允许写入操作
static、extern、const比较
static
- 全局静态变量
优点:无论对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会指向固定的指针地址,供所有对象使用,节省空间。
缺点:存在的生命周期长,从定义直到程序结束
建议:从内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的生命周期长,一直占用空间。程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢。
static int counter = 0;
//可以在一个类中多个方法中使用
void increment() { counter++; }
void print_counter() { printf("Counter: %d\n", counter); }
- 局部静态变量
优点:定义后只会存在一份值,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间,只能在该局部代码块中使用。
缺点:存在的生命周期长,从定义直到程序结束,只能在该局部代码块中使用。
建议:局部和全局静态变量从本根意义上没有什么区别,只是作用域不同而已。如果值仅一个类中的对象和类方法使用并且值可变,可以定义全局静态变量,如果是多个类使用并可变,建议值定义在model作为成员变量使用。如果是不可变值,建议使用宏定义。
void log_message(const char* msg) {static int log_count = 0;printf("[%d] %s\n", ++log_count, msg);
}//仅在该函数中可以使用,不可以被外界访问
extern全局变量
//.m中要定义
NSString* name;
//.h中同时也要定义
extern NSString* name;
- 对内的全局变量:没有用extern在.h中修饰的变量,仅定义在.m文件中让该变量只能在该类使用
优点:不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会存一份值,供所有对象使用,节省空间。跟全局静态变量一样,只是少了static修饰少了static特性。
缺点:存在的生命周期长,从定义直到程序结束。
建议:都跟全局静态变量都一样了,还需要用对内的全局变量吗?不用extern修饰就少了extern的特性,还不如用全局静态变量,至少能明确的知道static是对内使用的。
- 对外的全局变量:除了该类,其他文件也可以访问该变量
优点:除了该类,其他文件也可以访问该变量
缺点:存在的生命周期长,从定义到程序结束,并且外部可以修改其值,出现错误不容易定位。
建议:使用全局变量的原因在于其对外的特性,但是其使用的方便性没有使用model的属性或宏来得方便。程序启动的时候会单独加载全局变量,同理与全局静态变量,少使用。
const常量
不同于变量,常量的值是固定不可变的,一般用于只读值
- 优点:只可以读取值,不能修改。一般用于接口或者文字显示这种固定值。添加extern可以对外全局常量,任意位置都可以访问。
- 缺点:存在的生命周期长,从定义直到程序结束。需要在.h .m中分别定义代码较多。
- 建议:良好的编码习惯而言,少使用宏,多使用常量。因为常量声明是有明确类型的,而宏只是替换并不能进行类型判断。不够严谨。
//.h中定义extern
extern NSString *const name;
//.m中定义值
NSString *const name = @"123";//const声明部位不同,意义也不同。const定义的是其右边整体不可变。//*const name1定义的是 name1 不可变。 name1是指针。
//因此 不能通过修改name1而指向其他值。常规的const使用这个方法定义不可修改的值
NSString *const name1 = @"456";//const * name定义的是 * name不可变, 而*name指向的是@"789",
//也就是说@"789"这个内存地址的值不可变为其他值。
NSString const * name2 = @"789";
//但name指针可以指向其他值,所以该定义方式无法保证值的唯一性。
NSString *newName = @"222";
name2 = newName;