Swift 关于指针跟内存的理解

430 阅读4分钟

废话开篇:程序在运行的期间,根据数据跟逻辑的变化会不断的开辟内存地址跟销毁对象,当声明一个变量后,变量会保存在栈区,如果再在堆区开辟一段内存区域,并且把堆区的地址绑定到栈区的变量里,那么,指针就产生了。多个指针如果指向同一块堆区内存,那么,该堆区内存的引用计数就会增加,由于系统会处理栈区变量的销毁,那么,当堆区的某块内存没有指针指向的时候,就说明它是一块可以被销毁的内存区域了,销毁的时机就到了,至于怎么销毁,是ARC还是MRC,都是销毁外在手段,目的就是腾出该部分内存进行再次利用。

一、查看栈地址

声明 Test

这里声明一个 Test 类,外层包了一个结构体,目的是防止命名类名重复

image.png

Test 类里声明了两个属性

利用 withUnsafePointer 查看栈地址

image.png

定义 t、 t1 两个对象,分别打印一下栈地址。

image.png

可以看到栈区地址由高地址向低地址进行分配,依次分配8个字节。

二、利用指针偏移查看堆区数据

其实,栈区跟堆区都是内存地址,栈区是高到低分配,堆区是低到高分配,不管不同的区是如何进行分配的,那么,只要有具体内存地址,那么,就可以访问地址里存储的具体数据。

通过 Unmanaged.passUnretained().toOpaque() 获取某个对象的堆地址

image.png

打印结果

image.png

通指针偏移查看属性值

上面对 t1numname 属性进行了设置,那么,这里通过指针偏移进行查看。

在这之前,需要知道堆区首地址偏移多少才是对象的 num 存储的地址。

x/4gx命令:打印4个16进制地址

image.png

这里的 4 个地址中:

1地址存储的是对象的类名;

2 地址存储的是对象的引用计数;

3 地址存储的属性 num 值;

4 地址存储的属性 name 值;

通过地址偏移来看一下 3、4 地址的具体内容:

查看 num

image.png

num 对应的地址是 t1 首地址 heap1 偏移 16 得来的

打印结果:

image.png

查看 name

image.png

name 对应的地址是 t1 首地址 heap1 偏移 24 得来的,之所以是 24,是因为 num 的类型是 Int 它的步幅是8个字节。

打印结果:

image.png

这是之前对 t1 初始化进行的设置

image.png

可以看到通过地址偏移成功访问到了想要的数据。

三、内存对齐原则

image.png

这里将 test 类型的 numInt 类型转变为 Int? 可选类型会怎样呢?

直接运行:

image.png

崩溃了!在通过地址偏移访问 name 的数据时崩溃了,访问了野指针,这里的野指针的意思是访问的内容与预期的数据类型不匹配。

原因:Int? 可选类型比 Int 多出 1 个字节来,Int 本身是 8 字节,在多出 1 个字节后实际尺寸就变成了 9,在加上内存对齐的约束,那么,Int? 实际的步幅就是 16 个字节。

name 偏移 32 验证一下

image.png

打印结果:

image.png

可以看到成功的访问到了 name 数值。

查看 Int?Int 具体区别

Int?

image.png

打印结果:

image.png

可以看到,Int? 的步幅为 16

Int

image.png

打印结果:

image.png

可以看到,Int 的步幅为 8

所以,通过地址偏移获取数据的方法需要明确知道偏移的字节数,当然,实际开发中不会这样去操作的,更不是苹果所提倡的。这里仅仅是深化了解内存的而进行的演示。

四、类名的输出与引用计数的获取

那么,这里试试通过同样的方法获取一下类名:

image.png

打印如下:

image.png

但是,当以同样的方法进行引用计数的时候,便出现了问题,

image.png

这里是引用计数,如果这个是一个保存引用计数个数的内存数据,显然不能能有如此多!

这里附上一张图:

image.png

网上搜索之后需要将引用计数16进制数据进行右移 33 位获得,下面试一下:

image.png

看来引用计数数值存储是个复杂的过程,这里先做个记录。

总结与思考:个人记录,如有欠妥,欢迎指正与讨论[抱拳][抱拳][抱拳]。