此篇为针对Objective-c语言入门的基础知识,为了能让大家更清楚的理解,此整理中编写了许多的代码案例和部分截图,如有错误之处,望指正,愿与您相互交流学习,共同进步!---"会飞的猴子_阿新" (同时还要向刀哥致敬)
本篇目标是: 理解内存五大区域及各自的职责
目录结构
- 00.简述
- 01. 分配和释放(面试常被问到)
- 02.栈区和堆区
- 0.3 全局变量、静态变量和常量
- 本篇主要学习目标回顾:
- 结束语
00.简述
程序要想执行,第一步就需要 被加载到内存中
内存五大区域: 栈区,堆区,BSS段(静态区),常量区(数据段),代码段.
栈区
: 局部变量和方法实参堆区
:OC中使用new方法创建的对象,被创建对象的所有成员变量保存在堆区中.BSS段(也叫静态区)
:
教科书
:未被初始化的全局变量和静态变量.
Xcode8中
: 全局变量和静态变量,不管有没有被初始化,都存放在BSS段中.验证见本篇中的案例.常量区(也叫数据段)
:
教科书: 存储已经初始化的全局变量,静态变量,常量.
xcode8: 存储常量
代码段
: 程序的代码.
01. 分配和释放(面试常被问到)
-
栈区
(stack [stæk]): 由编译器自动分配释放- 局部变量是保存在栈区的
- 方法调用的实参也是保存在栈区的
-
堆区
(heap [hiːp]): 由程序员分配释放,若程序员不释放,会出现内存泄漏- 赋值语句右侧 使用 new 方法创建的对象
- 被创建对象的所有 成员变量
-
BSS 段
: 程序结束后由系统释放 -
数据段
: 程序结束后由系统释放 -
代码段
:程序结束后由系统释放程序 编译链接 后的二进制可执行代码
02.栈区和堆区
int main(int argc, const char * argv[]) {
// 局部变量是保存在栈区的
// 栈区变量出了作用域之后,就会被销毁
NSInteger i = 10;
NSLog(@"%zd", i);
// 局部变量是保存在栈区的
// 赋值语句右侧,使用 new 方法创建的对象是保存在堆区的
// xinge 变量中,记录的是堆区的地址
// 在 OC 中,有一个内存管理机制,叫做 `ARC`,可以自动管理 OC 代码创建对象的生命周期
// 因此,在开发 OC 程序的时候,程序员通常不需要考虑内存释放的工作
LJXPerson *xinge = [LJXPerson new];
NSLog(@"%@", xinge);
return 0;
}
10
<LJXPerson: 0x100404b70>
2.1 栈区
栈区 (stack [stæk]) : 由编译器自动分配释放
2.1.1 栈区中的保存(栈区的职责/存储的内容)
-
局部变量
-
方法实参
(eg:在main函数中,调用方法,方法中的实参)
2.1.2 栈区的特点
-
存储空间有限
. iphone的栈区大小只有512k
(默认) ,非常有限 -
连续性
. 栈区的地址是连续的
部分代码:
#import <Foundation/Foundation.h>
#import "LJXPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger i = 10;
NSLog(@"i 的栈区地址是 %p", &i);
NSInteger j = 10;
NSLog(@"j 的栈区地址是 %p", &j);
// xiaoming 变量中,存储的是 LJXPerson
对象的内存地址(堆区的内存地址)
LJXPerson *xinge = [LJXPerson new];
NSLog(@"xinge 的栈区地址是 %p", &xinge);
double height = 10;
NSLog(@"height 的栈区地址是 %p", &height);
.......
i 的栈区地址是 0x7fff5fbff768
j 的栈区地址是 0x7fff5fbff760
xinge 的栈区地址是 0x7fff5fbff758
height 的栈区地址是 0x7fff5fbff750
地址分配从大到小
. 栈区地址按照分配的顺序,由大到小顺序排列访问速度快
.系统管理
. (栈区的内存由系统管理)
2.1.3 其他
如果在程序中调用方法,会开启一个 " 栈帧 ".(这个栈帧可以理解为也是一块连续的区域)
栈帧的地址与之前的局部变量的地址不是连续的栈帧中记录实参地址,
以及方法内部的局部变量方法执行完毕后,栈帧销毁(弹栈)
<<<这样每次执行完毕后,都弹栈释放内存,这样就会始终保证栈区占用的内存不会特别大>>
so-课外话->我们在开发的时候,如果每个方法都写的很短,同时每个方法声明的变量都很少.
这样做一定会节约内存
见图知意
添加 "在一个函数/方法中"
即在一个函数/方法中最多可定义65536,--->因为"每次"执行完"一个"方法之后就会弹栈释放内存
栈的概念: 后进先出/ 先进后出
见图知意:
NSLog(@"NSInteger 变量占用的字节数 %tu", sizeof(NSInteger));
NSLog(@"double 变量占用的字节数 %tu", sizeof(double));
NSLog(@"xinge 变量占用的字节数 %tu", sizeof(xinge));
// 提示:栈区中,只适合存储非常小的数据
NSLog(@"在 iPhone 中,最多可以在一个函数/方法中定义 %zd 个局部变量", 512 * 1024 / 8);
// 测试方法调用时,实参在栈区的存储情况
[xinge sumWithNum1:i andNum2:j];
总结: 调用方法时栈区的工作原理
* 开启栈帧
* 保存实参
* 保存局部变量
* 方法完成后弹栈,销毁栈帧,释放空间
2.2 堆区
堆区 (heap [hiːp]): 由程序员分配释放,若程序员不释放,会出现内存泄漏
2.2.1 堆区中保存:
- 使用 new 方法创建的对象保存在堆区
- 被创建对象的所有成员变量保存在堆区中
堆区的职责是"解决栈区空间有限的问题"
* OC 使用`new`方法创建的对象
--->{由于 **ARC 管理机制**,OC 程序员通常不需要考虑对象的释放.}
(在 OC 中,有一个内存管理机制,叫做 ARC(自动引用计数)
可以自动管理 OC 代码创建对象的生命周期)
* C 语言使用 malloc、calloc、realloc 函数分配的空间,需要使用 free 函数释放
顾: 在开发 OC 程序的时候,程序员通常不需要考虑内存释放的工作
但是:如果在 OC 的代码中,如果使用到 C 语言分配空间的函数,则需要考虑释放内存
1. 堆区的大小由系统决定,包括:系统内存/磁盘交换空间...
2. 系统使用`链表`来管理堆区中的内存分配情况
3. {**程序员只需要负责堆区中内存的分配和释放工作**}
2.2.2能够说出堆区的特点
所有程序共享
存储大数据
程序员管理
: 堆区的内存需要程序员管理不连续
: 堆区的地址是不连续的速度没有栈区快
: 堆区的访问速度没有栈区快,因为我们要访问堆区中创建对象的属性, 必须先需要通过变量找到栈区的地址,再通过地址定位到到堆区中的某一个位置, 只有找个这个位置之后,我们才可以访问到存储到这个对象中属性对应的数值.由于有了 这个地址寻找的过程,所有速度没有栈区的快.
0.3 全局变量、静态变量和常量
3.1 全局变量/静态变量/常量保存的内存区域
开发要让 变化控制在有限的范围内
3.1.1 教科书中 全局变量 和 静态变量 的存储区域
有初始值的
全局变量 和 静态变量 保存在 数据段(常量区)没有初始值的
全局变量 和 静态变量 保存在 BSS 段(静态区)
当给 全局变量 或 静态变量 设置初始值后,会被移动到 数据段(常量区)
3.1.2 Xcode 8 中 全局变量 和 静态变量 的存储区域
-
无论是否设置初始值,
全局变量 和 静态变量
都保存在 BSS 段(静态区) -
常量
存储在 数据段(常量区)
注意:(全局/静态变量,常量)教科书与xcode8验证有点差别
(1) xcode8 "全局变量"实际存储区域案例验证:
案例代码:
#import <Foundation/Foundation.h>
NSInteger num1 = 10; //定义第一个全局变量 并且初始化
NSInteger num2; //定义第二个全局变量 没有初始化
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 提示:在 Xcode 8 中,全局变量无论是否初始化,地址保持不变
NSLog(@"第1个全局变量的地址%p", &num1); //第1个全局变量的地址0x100001188
NSLog(@"第2个全局变量的地址%p", &num2); //第2个全局变量的地址0x100001190
num2=100;
NSLog(@"第2个全局变量的地址%p(初始化后的)", &num2);
//第2个全局变量的地址0x100001190(初始化后的)
}
return 0;
}
第1个全局变量的地址0x100001188
第2个全局变量的地址0x100001190
第2个全局变量的地址0x100001190(初始化后的)
Program ended with exit code: 0
可见1:地址从小到大是连续的
可见2:
在 Xcode 8 中,全局变量无论是否初始化,地址保持不变
(2)xcode8 "静态变量"实际存储区域案例验证:
#import <Foundation/Foundation.h>
static NSInteger num1 = 10; //定义第一个全局变量 并且初始化
static NSInteger num2; //定义第二个全局变量 没有初始化
static NSInteger sNum1 = 10; //定义第一个静态的全局变量 并且初始化
static NSInteger sNum2; //定义第二个静态的全局变量 没有初始化
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 提示:在 Xcode 8 中,全局变量无论是否初始化,地址保持不变
NSLog(@"第1个全局变量的地址%p", &num1); //第1个全局变量的地址0x1000011f8
NSLog(@"第2个全局变量的地址%p", &num2); //第2个全局变量的地址0x100001208
num2=100;
NSLog(@"第2个全局变量的地址%p(初始化后的)", &num2);//第2个全局变量的地址0x100001208(初始化后的)
NSLog(@"##############################################");
NSLog(@"第1个静态的全局变量的地址%p", &sNum1); //第1个静态的全局变量的地址0x100001200
NSLog(@"第2个静态的全局变量的地址%p", &sNum2); //第2个静态的全局变量的地址0x100001210
sNum2=100;
NSLog(@"第2个静态的全局变量的地址%p(初始化后的)", &sNum2);//第2个静态的全局变量的地址0x100001210(初始化后的)
}
return 0;
}
第1个全局变量的地址0x1000011f8
第2个全局变量的地址0x100001208
第2个全局变量的地址0x100001208(初始化后的)
################################
第1个静态的全局变量的地址0x100001200
第2个静态的全局变量的地址0x100001210
第2个静态的全局变量的地址0x100001210(初始化后的)
Program ended with exit code: 0
可见1. 静态变量的初始化前后地址也是不变的
可见2. 静态变量和全局变量是交叉保存的
so-->面试注意:
---->在xcode 8中, 静态变量和全局变量保存在同一个区域
---->在xcode 8中, 静态变量和全局变量都保存在BSS段中.
跟我们教课书上是有区别的,
so-->当面试中遇到这个问题时,我们可以这样回答:
在教课书中:BSS段存放的是未被初始化的全局变量和静态变量,
但是我们通过xcode8 验证后,他们不管是全局变量
还是静态变量都存储在BSS段中.
(3)xcode8 "常量"实际存储区域案例验证:
#import <Foundation/Foundation.h>
NSInteger num1 = 10;
NSInteger num2;
static NSInteger sNum1 = 10;
static NSInteger sNum2;
const NSInteger cNum = 10000;//定义一个常量
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"第1个全局变量的地址%p", &num1);
NSLog(@"第2个全局变量的地址%p", &num2);
num2=100;
NSLog(@"第2个全局变量的地址%p(初始化后的)", &num2);
NSLog(@"##############################################");
NSLog(@"第1个静态的全局变量的地址%p", &sNum1);
NSLog(@"第2个静态的全局变量的地址%p", &sNum2);
sNum2=100;
NSLog(@"第2个静态的全局变量的地址%p(初始化后的)", &sNum2);
//第2个静态的全局变量的地址0x100001230(初始化后的)
NSLog(@"##############################################");
NSLog(@"第1个常量的地址%p", &cNum);
//第1个常量的地址0x100000e88
}
return 0;
}
第1个全局变量的地址0x100001218
第2个全局变量的地址0x100001228
第2个全局变量的地址0x100001228(初始化后的)
#################
第1个静态的全局变量的地址0x100001220
第2个静态的全局变量的地址0x100001230
第2个静态的全局变量的地址0x100001230(初始化后的)
##################
第1个常量的地址0x100000e88
Program ended with exit code: 0
常量存储演示:面试万一遇到,可演示一下,解释与科普书上的不同
可见: 全局变量和静态变量 跟常量放在不同的区域中,常量存放在常量区(或叫数据段).
* 教科书:数据段(常量区)用来存储已经初始化的全局变量,静态变量,常量.
* xcode8:中数据段(常量区)存放的是常量.
3.2 BSS段(静态区)
存储的内容: (前面已经案例验证过了):
1. 教科书: 存储未初始化
的 全局变量 和 静态变量
2. Xcode 8验证: 不管有没有初始化均存储 全局变量 和 静态变量
.
3.3 数据段(常量区)
存储的内容: (前面已经案例验证过了):
-
教科书 :存储
已经初始化
的 全局变量、静态变量、常量 -
Xcode 8验证 :不管有没有初始化
均存储 常量
.
3.4 全局变量与全局静态变量的区别
- 若程序只有一个文件,
全局变量
与全局静态变量
没有区别 (程序中只有一个文件,几乎是不存在的,因为通常一个类就对应2个文件.) -
若程序由多个文件构成时,
全局变量
与全局静态变量
不同:
- 全局静态变量 通常在定义该变量的文件内部使用 注意:
注意:
1.为什么OC中几乎不使用 全局变量?
1. 因为程序的任何一个位置都可以对 全局变量 进行修改
2. 一旦程序出现由全局变量产生的错误,不好排查错误
3. 使用全局变量不是一个好习惯
[我的理解:因为当我们开发时文件有很多,但是又不能把全局变量定义到头文件中
(如果定义在头文件中,当别的类引入该头文件后,相当于在两个类中都低定义了该全局变量,
会出现重复定义),所有几乎不使用.]
2.不能把 全局变量 定义在头文件中
(我的理解:如果定义在头文件中,当别的类引入该头文件后,相当于在两个类中都定义了该全局变量,会出现重复定义)
3.在程序开发时,全局变量 和 静态变量 都不能重名
(我的理解:当跟别的变量重名时,会被重名的变量修改.)
4.静态变量 的修改通常只是在 定义当前静态变量的文件 内部进行
外部即使修改,但是由于地址不同,也不会影响到
定义静态变量文件
内部的数值变化
一旦程序因为 静态变量 产生错误,只需要在当前文件中,检查一下修改 静态变量 的代码即可,排查错误难度相对降低
提示:在程序开发时,如果要使用全局的变量,应该在 .m 中定义一个全局的静态变量,而不要把该变量暴露在头文件中-->因为变量范围越小越可控.
------------------LJXPerson.h---------------------
#import <Foundation/Foundation.h>
//
static NSInteger num1 = 99; //定义一个全局静态变量
@interface LJXPerson : NSObject
/**
测试方法
*/
- (void)test;
@end
------------------LJXPerson.m---------------------
**LJXPerson.m**
#import "LJXPerson.h"
@implementation LJXPerson
- (void)test{
num1--;
NSLog(@"%p %zd",&num1,num1);
}
@end
------------------main.m-------------------------
#import <Foundation/Foundation.h>
#import "LJXPerson.h"
int main(int argc, const char * argv[]) {
//测试全局的静态变量
NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
num1=777777;
NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
LJXPerson *xinge=[LJXPerson new];
[xinge test];
LJXPerson *xiaoming=[LJXPerson new];
[xiaoming test];
NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
return 0;
}
在LJXPerson中定义的全局静态变量的地址0x100001190,99
在LJXPerson中定义的全局静态变量的地址0x100001190,777777
0x100001198 98
0x100001198 97
在LJXPerson中定义的全局静态变量的地址0x100001190,777777
通过:上面的输出结果截图也对刚才上面的阐述得到了验证,以下代码第6,7,8,13,14行是调用的外部的,即修改的是地址0x100001190对应的变量的值,只有在对象调用方法时是调用的内部的静态变量.
//测试全局的静态变量
6. NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
7. num1=777777;
8. NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
9. LJXPerson *xinge=[LJXPerson new];
10. [xinge test];
11. LJXPerson *xiaoming=[LJXPerson new];
12. [xiaoming test];
13. NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
14. return 0;
}
main函数中:注释掉下面的代码
// NSLog(@"在LJXPerson中定义的全局静态变量的地址%p,%zd", &num1,num1);
// num1=777777;
在LJXPerson中定义的全局静态变量的地址0x100001190,99
0x100001198 98
0x100001198 97
在LJXPerson中定义的全局静态变量的地址0x100001190,99
3.5 静态变量的正确用法
- 先定义局部静态变量
- static 关键字定义的变量在程序执行中
只会被执行一次
- 在 BSS 段为变量分配空间
- 并且设置初始值,如果没有指定初始值,会使用 0 作为初始值
即: static 关键字的作用
- 在 BSS 段为 静态变量 分配空间
- 为 静态变量 设置初始值,如果没有指定初始值,会使用 0 来作为初始值
- static 关键字定义静态变量的代码,只会被执行一次
------------------声明---------------------
#import <Foundation/Foundation.h>
@interface LJXPerson : NSObject
/**
测试方法
*/
- (void)test;
@end
-------------------实现--------------------
#import "LJXPerson.h"
@implementation LJXPerson
- (void)test{
static NSInteger num1 = 99; //定义局部静态变量
num1--;
NSLog(@"%p %zd",&num1,num1);
}
@end
-------------------主程序--------------------
#import <Foundation/Foundation.h>
#import "LJXPerson.h"
int main(int argc, const char * argv[]) {
LJXPerson *xinge=[LJXPerson new];
[xinge test];
LJXPerson *xiaoming=[LJXPerson new];
[xiaoming test];
return 0;
}
2030-03-31 15:12:48.504 demo[5399:535229] 0x100001170 98
2030-03-31 15:12:48.505 demo[5399:535229] 0x100001170 97
Program ended with exit code: 0
3. 如果当前类文件中,有 多个方法
使用到该静态变量,再将该静态变量修改成全局的
------------------声明---------------------
#import <Foundation/Foundation.h>
@interface LJXPerson : NSObject
/**
测试方法
*/
- (void)test;
- (void)demo;
@end
-------------------实现--------------------
#import "LJXPerson.h"
//当前类文件中,有 `多个方法` 使用到该静态变量,放到全局中
static NSInteger num1 = 99;
@implementation LJXPerson
- (void)test{
num1--;
NSLog(@"%p %zd",&num1,num1);
}
- (void)demo{
num1++;
NSLog(@"%p %zd",&num1,num1);
}
@end
-------------------主程序--------------------
#import <Foundation/Foundation.h>
#import "LJXPerson.h"
int main(int argc, const char * argv[]) {
LJXPerson *xinge=[LJXPerson new];
[xinge test];
[xinge demo];
LJXPerson *xiaoming=[LJXPerson new];
[xiaoming test];
[xiaoming demo];
return 0;
}
0x1000011b0 98
0x1000011b0 99
0x1000011b0 98
0x1000011b0 99
3.6 常量
作用:定义一个
固定不变
的值,全局统一使用,eg:公司网址,电话等
-
const 关键字保证其后修饰的常量的值不允许被修改
常量的工作原理:
在 程序被加载到内存时,就会为常量分配空间并且设置初始值
在 数据段 为常量分配空间
如果没有指定初始值,会使用 0 作为初始值 -
不能把 常量 定义在头文件中
(跟全局变量不能定义在头文件中一样,会导致重复定义) -
应该在 .m 中定义常量
比如我们在LJXPerson.m中定义了一个常量,const NSInteger cNumber =100;这时,这个常量只能在LJXPerson.m中使用,怎么才能也能在main.m中使用呢,
那就需要下面介绍的第4个知识点了.
在LJXPerson.h
头文件中做声明.extern
const NSInteger cNumber;
特别注意,不能再给设置初始值了,
它已经在其他文件中(eg:在LJXPerson.m中设置值为100了),我们要清楚extern的作用,表示该常量的数值,是在其他文件中设置的,外部可以直接使用此常量)
总之:eg:在.m中: const NSInteger cNumber =100;
eg:在.h中: extern const NSInteger cNumber;
-
在 .h 中使用 extern 关键字声明该 常量 在其他文件设置值
extern的作用: extern关键字,表示该常量的数值,是在其他文件中设置的,
外部可以直接使用此常量.
注意:在一个项目中,常量不能重名,因此在定义常量时,应该尽量长,有前缀,以保证不会出现重名的情况
本篇主要学习目标回顾:
内存五大区域
程序要执行,首先需要被加载到内存
1.1 记忆内存五大区域的名称
能够说出内存五大区域的名称
栈区
堆区
BSS段(静态区)
数据段(常量区)
代码段
1.2 记忆栈区/堆区的职责及特点
能够说出栈区职责(存储的内容)
- 局部变量
- 方法实参
能够说出栈区的特点
512K
连续
从大到小
速度快
系统管理
能够说出调用方法时栈区的工作原理
开启栈帧
保存实参
保存局部变量
方法完成后弹栈,销毁栈帧,释放空间
能够说出堆区的职责(存储的内容)
- OC 使用 new 方法创建的对象 由于 ARC 管理机制,OC 程序员通常不需要考虑对象的释放
- C 语言使用 malloc 函数分配的空间,需要使用 free 函数释放
能够说出堆区的特点
所有程序共享
存储大数据
程序员管理
不连续
速度没有栈区快
1.3 记忆全局变量/静态变量/常量保存的内存区域
开发要让 变化控制在有限的范围 内
能够说出教科书中 全局变量 和 静态变量 的存储区域
- 有初始值 的 全局变量 和 静态变量 保存在 数据段(常量区)
- 没有初始值 的 全局变量 和 静态变量 保存在 BSS 段(静态区) 当给 全局变量 或 静态变量 设置初始值后,会被移动到 数据段(常量区)
能够说出 Xcode 8 中 全局变量 和 静态变量 的存储区域
- 无论是否设置初始值,全局变量 和 静态变量 都保存在 BSS 段(静态区) 能够说出 常量 的存储区域 常量 存储在 数据段(常量区) 能够说出为什么几乎不使用 全局变量 开发要让 变化控制在有限的范围 内 不能把 全局变量 定义在头文件中,否则会出现重复定义 ## 1.4 记忆静态变量/常量的用法
能够说出 static 关键字的作用
- 在 BSS 段为 静态变量 分配空间
- 为 静态变量 设置初始值,如果没有指定初始值,会使用 0 来作为初始值
- static 关键字定义静态变量的代码,只会被执行一次!
能够说出静态变量的正确用法
* 如果只有一个方法使用,将 静态变量 定义在方法内部
* 如果有多个方法使用,将 静态变量 定义在 .m 中
* 不要把静态变量定义在头文件中
能够说出常量的作用
定义一个固定不变的值,全局统一使用,例如公司的网址/电话等...
在 iOS 开发中,通常仅仅在定义 通知字符串 时才会使用到常量
能够说出常量的正确用法
在 .m 中定义常量并且设置初始值
const NSInteger cNum = 99;
在 .h 中使用 extern 关键字声明常量在其他位置定义并且已经赋值,外部可以直接使用
extern const NSInteger cNum;
常量名应该尽量的长以避免出现重名
结束语
ok! 以上关于内存五大区域基础知识对您起到学习作用了吗?如果有错误之处望指正!谢谢!相关学习共同进步!真心希望其中某个知识点能帮到您!
以上内容目录结构 (点击可调转)