iOS 内存分区

发布于:2025-05-19 ⋅ 阅读:(16) ⋅ 点赞:(0)

iOS内存分区

前言

笔者之前学习OC源码的时候,发现对于这里的几个static,extern,const的内容有遗忘,所以这里笔者重新学习一下有关于内存分区的内容

五大分区

image.png

  1. 栈是由编译器自动分配释放来管理内存。用户存放程序临时创建的变量、存放函数的参数值、局部变量等。由于栈的先进后出特点,所以特别适合用来做保存/恢复现场的操作。从这个意义上,我们可以把栈看做一个临时寄存、交换的内存区。
  • 是一段连续的内存区域,从高地址向低地址存储,遵循先进后出(FILO)原则。 一般在运行时进行分配,内存空间由系统管理,变量过了作用域范围后内存便会自动释放。参数、函数、局部变量都放在栈区。参数入栈是从前往后入栈;而结构体入栈是从后往前入栈.
  • 优点: 栈是系统数据结构,对应线程/进程是唯一的。 快速高效,缺点是有限制,数据不灵活。[先进后出] 栈空间分静态分配和动态分配两种。 静态分配是编译器完成的,比如自动变量(auto)的分配。 动态分配由alloc函数完成。 栈的动态分配无需释放(是自动的),也就没有释放函数。 为可移植的程序起见,栈的动态分配操作是不被鼓励的!

在OC中我们创建一个对象是这样存储的

NSObject *obj = [NSObject alloc];

这里我们注意这里是分级存储的,obj这个指针其实是栈区创建的,但是我们的alloc这个方法其实是在堆区分配的内存.这个new做的事情有一下几个步骤:

  1. 在堆内存中申请一块合适大小的空间
  2. 在申请的这块空间中根据类的模板创建对象
  3. 初始化对象的属性,为对象的属性赋默认值 ->如果属性的类型是基本数据类型,就赋值为0 ->C指针类型 NULL ->OC指针类型 nil
  4. 返回这个对象在堆空间的地址 将这个地址赋值给obj指针 obj指针指向了堆空间中的NSObject对象

在这里插入图片描述

NULL和nil的区别如下:

类型 定义 适用场景
NULL C/C++ 中的空指针宏,通常定义为 (void*)00 用于 非对象指针(如基本数据类型指针、结构体指针等)。
nil Objective-C 中表示空对象的指针,定义为 (id)0 专用于 Objective-C 对象(如 NSString *str = nil)。

使用场景区别

场景 NULL nil
集合类型存储 不可用(非对象类型) 不可直接存储(nil 是集合结束标志) 需用 NSNull 替代空值3****9
指针类型操作 用于释放后指针置空(如 free(ptr); ptr = NULL; 用于对象释放(如 obj = nil;1****5
安全性与兼容性 可能引发野指针崩溃(如访问已释放内存) 安全(消息发送自动忽略)

NSNull和nil的区别:

  • nil:空对象(内存已释放),不能加入集合。
  • NSNull:单例对象([NSNull null]),用于在集合中表示空值占位
  1. 堆区,堆区由程序员分配和释放,如果程序员不释放,程序结束后可能由操作系统回收,比如变量通过new,alloc,malloc分配的内存块就在堆区
    • 堆向高地址扩展的数据结构,是不连续的内存区域。 程序员负责在何时释放内存,在ARC程序中,计数器为0的时候,在当次的runloop结束后,释放掉内存。堆中的所有东西都是匿名的,这样不能按名字访问,而只能通过指针访问。 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续性,从而造成大量的碎片 ,使程序效率降低。 (是用链表组织的不连续的,空闲的内存区域)
    • 优点: 灵活方便,数据适应面广泛,但是效率有一定降低。[顺序随意] 堆是函数库内部数据结构,不一定唯一。 不同堆分配的内存无法互相操作。 堆空间的分配总是动态的。 虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存,释放内存匹配是良好程序的基本要素
  2. 常量区 文字常量区:存放常量字符串,程序结束后由系统释放
    • 该区是编译时分配的内存空间,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放。 存放常量:整型、字符型、浮点、字符串等。
  3. 静态区 全局区(静态区) (static): 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
    • 注意:全局区又可分为未初始化全局区(BSS段)和初始化全局区(DATA段)。
    • 全局变量是在程序刚刚创建的时候就赋值的而静态变量则是在使用的时候赋值的
  4. 代码区 程序代码区: 用来存放函数的二进制代码。 代码段需要防止在运行时被非法修改,所以只允许读取操作,而不允许写入操作。

static、extern、const关键字比较

const

const最好理解,由const修饰的变量不可以被显式修改的.

const是用来修饰常量的,这一点注意与宏的区别

1.编译时刻:宏是预编译(编译之前处理),const是编译阶段。

2.编译检查:宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。

3.宏的好处:宏能定义一些函数,方法。 const不能。

4.宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换。

static

static 修饰则意味着该变量仅在定义此变量的编译单元中可见, 不会导致其他单元重复导致命名冲突, 当编译器编译到此单元时, 就会输出一份 “目标文件”(object file)

  1. 在全局变量前加static,全局变量就被定义成为一个全局静态变量(全局变量和今天全局变量的生命周期是一样的,都是堆中的静态区,在整个工程执行期间一直存在).特点如下:

    • 存储区:静态存储区没变(静态存储区在整个程序运行期间都存在)
    • 作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局 变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。

    static修饰全局变量

    static NSString* string = @"123"; // 不会被其他文件访问修改,其他文件中可以使用相同名字的变量,不会发生冲突
    @interface ExternModel : NSObject
    @end
    
  2. static修饰局部变量,在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量:

    • 存储区:有栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它;
    • 作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

    例子:

  • - (void)incrementCounter {
        static NSInteger counter = 0; // 静态局部变量
        counter++;
        NSLog(@"计数器值: %ld", (long)counter);
    } //这个函数中的counter值不可以被外界访问,作用域仅限于函数内
    

3.static修饰函数
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用
好处:

  • 其他文件中可以定义相同名字的函数,不会发生冲突

  • 静态函数不能被其他文件所用。

extern

extern 用于声明外部全局变量或函数,允许跨文件访问.这个关键字只能用于修饰全局变量是一个大家很常见的东西,这里我们从三个方面来讲解:

  • 与.h文件的关系
  • extern引用变量
  • extern声明
与.h文件的关系

我们可以在.h文件中声明一个全局变量:

@interface ExternModel : NSObject
extern NSString* nanString;
@end

这里我们需要在.m文件中赋值:

@implementation ExternModel
NSString *nanString = @"hello"; // 如果在.h文件中声明了extern全局变量,那么在同一个类中的.m文件对全局变量的赋值必须是:数据类型+变量名(与声明一致)= XXXX结构。并且在调用的时候,必须导入.h文件
@end

调用的时候:例如:在viewController.m中调用,则可以引入:ExternModel.h,否则无法识别全局变量。当然也可以通过不导入头文件的方式进行调用(通过extern调用)。

extern引用变量

extern具备与.h文件相似的跨文件访问的功能,但是,h文件不局限与全局变量,而extern则必须是全局变量.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        extern NSString* nanString;
        NSLog(@"%@", nanString);
        NSLog(@"Hello, World!");
    }
    return 0;
}
extern声明

extern声明,仅适于修饰全局变量,不能去修饰其他的变量。一般情况下默认,定义的全局变量都带有extern,所以不过多解释。static

static和const联合使用

  • static与const作用:声明一个只读的静态变量
  • 开发使用场景:在一个文件中经常使用的字符串常量,可以使用static与const组合

extern和const联合使用

extern与const组合:只需要定义一份全局变量,多个文件共享。并且全局常量只被定义一次,分配一次内存空间。