平时我们编写程序的时候可能不太在意系统的内存问题,那么我们所编写代码变量都是怎么在内存中存取的呢?内存其实分为五大分区,栈区(系统管理的地方)、堆区(程序员控制的地方)、常量区(全局区)、静态区和代码区,下面我们来简单介绍介绍。
首先我们要知道,这里说的内存指的就是RAM。
一、五大分区
1.栈区
创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区。
里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。
优点:
- 栈是系统数据结构,对应线程/进程是唯一的。
- 快速高效,缺点是有限制,数据不灵活。[先进后出]
- 栈空间分静态分配和动态分配两种。
- 静态分配是编译器完成的,比如自动变量(auto)的分配。
- 动态分配由alloc函数完成。
- 栈的动态分配无需释放(是自动的),也就没有释放函数。
- 为可移植的程序起见,栈的动态分配操作是不被鼓励的!
@interface TestObject()
@end
@implementation TestObject
- (void)testMethodWithName:(NSString *)name {
NSLog(@"name指针地址:%p, name指针指向的对象内存地址:%p", &name, name);
Person *person = [Person new];
}
2.堆区
就是那些由new 、alloc 、malloc 、realloc 创建的对象所分配的内存块,它们的释放系统不会主动去管,由我们的开发者去告诉系统什么时候释放这块内存(一个对象引用计数为0时系统就会回销毁该内存区域对象)。一般一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操作。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。堆可以动态地扩展和收缩。
注意:全局区又可分为未初始化全局区:bss段 和 初始化全局区:data段。
优点:
- 灵活方便,数据适应面广泛,但是效率有一定降低。[顺序随意]
- 堆是函数库内部数据结构,不一定唯一。
- 不同堆分配的内存无法互相操作。
- 堆空间的分配总是动态的。
- 虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存,释放内存匹配是良好程序的基本要素。
Person *person = [[Person alloc] init];
3.常量区
存放常量 (整型、字符型,浮点型,字符串等),整个程序运行期不能被改变 。
常量:
已初始化的全局变量 已初始化的静态变量
4.静态区
全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。
- 内存空间也是由系统管理 -->
程序启动时开辟 ,程序结束时回收 ,程序执行期间一直存在。 static 修饰的变量仅执行一次,生命周期为整个程序运行期 。
5.代码区
存放函数的二进制代码。
NSString *string1;
NSString *string2 = @"1234";
static NSString *string3;
static NSString *string4 = @"1234";
static const NSString *string5 = @"654321";
- (void)test {
int a;
a = 10;
NSStirng *str;
str = @“1234”;
static NSString *str1;
static NSString *str2 = @"4321";
NSString *str3;
str3 = [[NSString alloc] initWithString:@"1234"];
}
二、static、extern、const比较
1.static静态变量
静态变量有两种
static NSString *name;
优点: 不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会指向固定的指针地址,供所有对象使用,节省空间。 缺点: 存在的生命周期长,从定义直到程序结束。 建议: 从内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的生命周期长,一直占用空间。程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢。
优点: 定义后只会存在一份值,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间,只能在该局部代码块中使用。 缺点: 存在的生命周期长,从定义直到程序结束,只能在该局部代码块中使用。 建议: 局部和全局静态变量从本根意义上没有什么区别,只是作用域不同而已。如果值仅一个类中的对象和类方法使用并且值可变,可以定义全局静态变量,如果是多个类使用并可变,建议值定义在model作为成员变量使用。如果是不可变值,建议使用宏定义。
2.extern全局变量
全局变量有两种
NSString *name;
extern NSString *name;
- 对内的全局变量:没有用extern在.h中修饰的变量,仅定义在.m中让该变量只能在该类使用
优点: 不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会存一份值,供所有对象使用,节省空间。跟全局静态变量一样,只是少了static修饰少了static特性。 缺点: 存在的生命周期长,从定义直到程序结束。 建议: 都跟全局静态变量都一样了,还需要用对内的全局变量吗?不用extern修饰就少了extern的特性,还不如用全局静态变量,至少能明确的知道static是对内使用的。
优点: 除了该类,其他文件也可以访问该变量。 缺点: 存在的生命周期长,从定义直到程序结束。并且外部可以修改其值,出现错误不容易定位。 建议: 使用全局变量的原因就在于其对外的特性,但是其使用的方便性没有使用model的属性或宏来得方便。程序启动的时候会单独加载全局变量,同理与全局静态变量,少使用。
全局静态变量与全局变量 其实本质上是没有区别的,只是存在修饰区别,一个static让其只能内部使用,一个extern让其可以外部使用。
3.const常量
不同于变量,常量的值是固定不可变的,一般用于只读值。 优点: 只可以读取值,不能修改。一般用于接口或者文字显示这种固定值。添加extern可以对外全局常量,任意位置都可以访问。 缺点: 存在的生命周期长,从定义直到程序结束。需要在.h .m中分别定义代码较多。 建议: 良好的编码习惯而言,少使用宏,多使用常量。因为常量声明是有明确类型的,而宏只是替换并不能进行类型判断。不够严谨。
extern NSString *const name;
NSString *const name = @"123";
NSString *const name1 = @"456";
NSString const * name2 = @"789";
NSString *newName = @"222";
name2 = newName;
三、注意
- 当一个 app 启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)。
- 在 iOS 中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的。
|