内存-①基础

①基础

[toc]

知识架构

iOS知识库

Android知识库

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1、几个本质
>
2、空指针和未初始化的指针
(1)、空指针和未初始化的指针的区别:
(2)、为什么指针变量定义时一定要初始化?
>
3、野指针与悬空指针
(1)、指针与内存的常见使用顺序
(2)、内存泄漏的概念
(3)、野指针概念
(4)、在iOS中野指针的后果
(5)、分析野指针的产生原因及解决办法
>
4、iOS NSerror 用双重指针理解
>
5、指针和引用的区别
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1、堆和栈的区别?
>
2、浅拷贝和深拷贝的区别
>
3、分析NSString、NSMutableString等类的copy、mutableCopy
(1)、分别对NSString、NSMutableString进行copy、mutableCopy生成的类型是什么?
(2)、分别对NSString、NSMutableString进行copy、mutableCopy操作,是否会开辟新地址,即是属于深拷贝还是浅拷贝?
(3)、将NSString、NSMutableString变量赋值给用copy、strong修饰的NSString属性的时候,是否会开辟新地址,即是属于深拷贝还是浅拷贝?
(4)、将NSString、NSMutableString变量赋值给用copy、strong修饰的NSMutableString属性的时候,是否会开辟新地址,即是属于深拷贝还是浅拷贝?
(5)、自己代码实现copy修饰符,应该怎么写????
>
4、NSCoding和NSCopy
(1)、NSCoding的作用
(2)、NSCopy
>
5、@synthesize和@dynamic区别
>
1
2
3
4
5
6
7
8
9
10
11
12
13
1、内存管理
(1)、在ObjC中,对象什么时候会被释放(或者对象占用的内存什么时候会被回收利用)?
(2)、那怎么知道对象已经没有被引用了呢?
(3)、autorelease、autoreleasepool(自动释放池)
(4)、autoreleasepool(自动释放池) 
(5)、autoreleasepool里面对象的内存什么时候释放?
(6)、runloop、autorelease pool以及线程之间的关系
(7)、自动释放池怎样创建
(8)、自动释放池使用注意
(9)、自动释放池的应用/什么时候要用@autoreleasepool
>
2、如何监测内存泄漏
>
1
2
自动释放池底层怎么实现?
>

常用数据类型占用内存大小

64位编译器

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节

一、指针

< 返回目录

1、几个本质

1
2
3
4
5
6
7
8
9
10
11
12
13
数据类型:
数据类型的本质是固定大小内存的别名。
对变量声明数据类型,是为了告诉编译器分配几个字节的内存。

变量:
变量的本质是一段内存空间的别名。
也就是给一段内存空间取一个新的名字,就是变量。

指针:
指针也是一种数据类型,它的值是某一个内存空间的地址。
指针的步长根据它指向的内存空间的数据类型而定。

数组中[]的本质:假如有数组array,则array[i]等价于*(array+i),是因为[]对于程序员来讲是友好的,但是编译器最终还是要将它理解为指针,也就是数组作为函数参数时的退化。array[i] ==> array[0+i] ==>*(array+i)

2、空指针和未初始化的指针/野指针

(1)、空指针和未初始化的指针的区别:

①空指针可以确保不指向任何对象或函数;
②未初始化指针则可能指向任何地方,即它所指向的地址就是随机的,也就说此时它是个野指针。(附:如果一个指针的指向对象后来被删除,却未置为空指针nil,则它也是野指针)
所以空指针在概念上不同于未初始化的指针。
对于malloc在其内存分配的时候,如果内存分配成功,返回的一定不是空指针;但是如果malloc内存分配失败,返回的空指针。而不是一个未初始化的指针。

以下是华为笔试题:

1
2
3
4
5
下面有关空指针和未初始化指针,说法错误的是?
A.对0x0这个地址取值是非法的
B.空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。
C.空指针与任何对象或函数的指针值都不相等
D.malloc在其内存分配失败时返回的是一个未初始化的指针

错误答案是D,因malloc内存分配失败,返回的是空指针。详细请查看原文地址
华为笔试:下面有关空指针和未初始化指针,说法错误的是?

(2)、为什么指针变量定义时一定要初始化?

答:因为你首先要理解一点.内存空间不是你分配了才可以使用,只是你分配了之后使用才安全。

为什么要进行对他初始化呢,因为如果你没对它初始化,那么这个指针所指向的地址就是随机的,即此时它是个野指针。这时候如果你引用这个指针并对它做了修改这个指针所指向的内容的操作的话,如果刚好这个指针所指向的内容恰好是另外一个程序的数据的话,那么你原本随意的一个修改,就造成了对另一个程序的数据的修改了,也就会导致另外一个程序可能不能正常运行了。所以使用前一定要进行初始化。

3、野指针与悬空指针

在C/C++等语言中,

悬空指针(Dangling Pointer)指的是:一个指针的指向对象已被删除,那么就成了悬空指针。

野指针是那些未初始化的指针

有时也把野指针和悬空指针通称悬空指针。
而好像在iOS中是通称为野指针。

以下内容摘自:百度百科:迷途指针

在计算机编程领域中,迷途指针,或称悬空指针、野指针,指的是不指向任何合法的对象的指针。

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针。

若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。

这种类型的程序错误,不容易找到问题的原因,通常会导致存储器区块错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。

某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现。

(1)、指针与内存的常见使用顺序

在堆中申请了一块内存,并用一个指针指向它。
一般我们都会在不用的时候先释放该指针指向的内存,再将该指针置为空指针。
即一般正确的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//指针变量和指针所指向的内存变量是两个不同的概念
//使用动态内存分为三步
//1.定义时,将指针为定义NULL
//2.释放内存时,把指针变量重新赋值或者NULL
//3.释放内存后,把指针变量赋值为NULL

#include <stdio.h>

int main02()
{
int *p = NULL;
p = (int *)malloc(4);

if (p != NULL){
free(p);//释放P所指向的内存空间,但指针变量p仍然留在栈中,成为了野指针
p = NULL;//释放野指针(这是关键,记得释放指针所指向的内存空间后,要释放野指针)
}

return 0;
}

但是
①、如果我们在未来始终没有去手动释放掉我们开辟的内存的话,会导致内存泄漏;
②、如果释放掉了该内存,却忘了同时释放只想该内存的指针,会导致产生悬空指针或者说是迷途指针,或者有人也称是野指针。
③、如果记得释放指针,却忘了释放指针只想的内存(即记得②忘了①),那么由于指针已经消失,而指针指向的东西还在,那么久永远无法控制这块内存,而导致一定内存泄漏了。

(2)、内存泄漏的概念

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

(3)、野指针概念

C语言: 当我们声明1个指针变量,没有为这个指针变量赋初始值.这个指针变量的值是1个垃圾指针 指向1块随机的内存空间。

OC语言: 指针指向的对象已经被回收掉了.这个指针就叫做野指针.

野指针:指向内存被释放的内存或者没有访问权限的内存的指针

更详细的概念可查看:百度百科——野指针

(4)、在iOS中野指针的后果

野指针的后果:崩溃EXC_BAD_ADDRESS

(5)、分析野指针的产生原因及解决办法

知识点:任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

“野指针”的成因主要有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1)指针变量没有被初始化。(即随机只想的这个指针很有可能只想一块没人用的内存,)
char *p; //此时p为野指针
//正确的为在声明之后加上 p=new char[10];我们常直接写为一行,即char *p=new char[10];

2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.
char *p=new char[10]; //指向堆中分配的内存首地址,p存储在栈区
cin>> p;
delete []p; //p重新变为野指针
//正确的应该在指针p被free或者delete之后,加上p = Null;

3)指针操作超越了变量的作用范围。
char *p=new char[10]; //指向堆中分配的内存首地址
cin>> p;
cout<<*(p+10); //可能输出未知数据

iOS 关于僵尸对象和僵尸指针的那些事儿

4、iOS NSerror 用双重指针理解

常见代码如下:

1
2
3
4
5
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:absoluteFilePath error:&error];
if (error) {
NSLog(@"删除单个文件的时候出现错误:%@",error.localizedDescription);
}

可见,如果我们不是传指针的指针&error,而是传error,那么if(error)中的error就肯定是nil了。那就没用了。

所以这里传error的指针的原因可简单概括为:
因为我们要得到一个新的error值。所以如果有方法

1
2
3
4
5
- (NSError *)getNewErrorForremoveItemAtPathremoveItemAtPath:(NSString *)path  error:(NSError **)error {
NSError *newError = [NSError errorWithDomain:...];

return newError;
}

那么这边,我们的error,就可以直接传error本身,甚至不传都是可以的。

但是实际是这些方法本身的返回值,已经被定义为判断能否进行某种操作,而不是操作是否成功给占用了,如这边已经被判断能否进行删除文件给占用了,所以如果我们还想知道这个删除文件操作结果的error,那就把error的指针的指针传进去,最后其出来的就是我们想要的。
所以,猜测其内部结构应该是

1
2
3
4
5
6
7
8
9
10
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error {
BOOL canRemove = ...;
if (!canRemove) {
* error = [NSError errorWithDomain:...];
return NO;
} else {
//* error = * error; //即不变
return YES;
}
}

以下解释原因摘自:ios中处理错误为什么传递的是&error,而不是error?

因为 需要将error 传入后修改其值,然后再返回来,返回来后还要保证己经修改过了。

&error传入是传的地址引用,传入后处理函数直接访问变量的地址,可以修改其值再返回同一个地址, 调用函数就可以知道值是否有修改,即是否有错。

而error传入是传的值引用,值引用传入到程序栈中后其实是把原来的值复制了一份传过去,处理函数可以修改,但无法将改后的值传出函数体。

5、指针和引用的区别

(1)定义和性质的区别
①指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
②而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
简单点说:一个是存地址,一个是变量别名
(2)指针和引用作为函数参数进行传递时的区别
①用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址
②引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

二、内存

< 返回目录

1、堆和栈的区别?

1
2
3
4
5
6
7
一、堆栈空间分配区别:
1、栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

二、堆栈缓存方式区别:
1、栈使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

八、谈谈内存管理、内存泄露、循环引用

< 返回目录

ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露。

1、内存管理

(1)、在ObjC中,对象什么时候会被释放(或者对象占用的内存什么时候会被回收利用)?

答案是:当对象没有被任何变量引用(也可以说是没有指针指向该对象)的时候,就会被释放。

(2)、那怎么知道对象已经没有被引用了呢?

ObjC采用引用计数(reference counting)的技术来进行管理:

1
2
3
4
1)每个对象都有一个关联的整数,称为引用计数器;
2)当代码需要使用该对象时,则将对象的引用计数加1;
3)当代码结束使用该对象时,则将对象的引用计数减1;
4)当引用计数的值变为0时,表示对象没有被任何代码使用,此时对象将被释放。

与之对应的消息发送方法如下:

对象操作 OC中对应的方法 引用计数的变化
当对象被创建时 alloc/new/copy/mutableCopy等 +1
持有对象 retain +1
释放对象 release -1
废弃对象 dealloc -
1
2
3
4
5
6
7
8
1)当对象被创建(通过alloc、new或copy/mutableCopy等方法)时,其引用计数初始值为1;
2)给对象发送retain消息,其引用计数加1;
3)给对象发送release消息,其引用计数减1;
4)当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象
当创建一个对象的实例并在堆上申请内存时,对象的引用计数就为1;
在其他对象中需要持有这个对象时,就需要把该对象的引用计数加1;
需要释放一个对象时,就将该对象的引用计数减1;
直至对象的引用计数为0,对象的内存会被立刻释放。
1
2
3
4
5
6
7
8
9
10
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSObject *object = [[NSObject alloc] init]; // 执行后,引用计数 = 1
NSLog(@"\n 引用计数 = %lu \n 对象内存 = %p \n object指针内存地址 = %x", (unsigned long)[object retainCount], object, &object);
self.property = object; // 执行后,引用计数 = 2
NSLog(@"\n 引用计数 = %lu \n 对象内存 = %p \n object指针内存地址 = %x \n property指针内存地址 = %x", (unsigned long)[object retainCount], object, &object, &_property);
[object release]; // 执行后,引用计数 = 1
NSLog(@"\n 引用计数 = %lu \n 对象内存 = %p \n object指针内存地址 = %x \n property指针内存地址 = %x", (unsigned long)[object retainCount], object, &object, &_property);
return YES;
}

当需要释放强引用指向的对象时,需要保证所有指向对象强引用置为 nil。__strong 修饰符是 id 类型和对象类型默认的所有权修饰符。

__weak 表示弱引用,对应定义 property 时用到的 weak。弱引用不会影响对象的释放,而当对象被释放时,所有指向它的弱引用都会自定被置为 nil,这样可以防止野指针。

2、如何监测内存泄漏

如果内存管理不当,势必会造成内存泄露。那我们如何快速的来找出内存泄露呢?以前我们可能会使用Instruments来监测,但是我们会发现使用Instruments特别繁琐,而且不一定能定位到内存泄露。

所以这里伟大的Facebook工程师们开源了一些自动化工具来解决监测内存泄露问题:FBRetainCycleDetector、FBAllocationTracker、FBMemoryProfiler。详情查看在iOS上自动检测内存泄露

你在开发大型项目时,如何进行内存泄露检测的?

instruments下有个leaks工具,启动此工具后,运行项目,工具里可以显示内存泄露的情况,双击可找到源码位置,可以帮助进行内存泄露的处理。

END

< 返回目录

1
2