swift指针内存管理-指针类型使用
创始人
2024-02-10 17:20:43

为什么说指针不安全

  • 我们在创建一个对象的时候,是需要在堆上开辟内存空间的
    但是这个内存空间的声明周期是有限的
    也就意味着如果使用指针指向这块内存空间,当这块内存空间的生命周期结束(引用计数为0),那么当前的指针就变成未定义的了

  • 创建的内存空间是有边界的,通过指针访问的内存空间超过已开辟内存空间的边界,也就是访问了一个未知的内存空间

  • 指针类型与内存的值类型不一致,也不安全,这一点参考 swift指针&内存管理-内存绑定

指针类型

Swift中的指针分为两类 typed pointer(指定指针数据类型) & raw pointer(原生指针-未指定指针数据类型)

在这里插入图片描述

如果需要开辟一段连续的内存空间,就可以使用 unsafeBufferPointer, 当然了unsafeMutableBufferPointer 就是可变的

连续的原生内存空间 unsafeRawBufferPointer / unsafeMutableRawBufferPointer , 这种指针需要结合 指针内存绑定来使用

原始指针-rawPointer 的使用

如何使用 rawPointer 来存储4个整型的数据

在存储之前,先了解几个概念

print("MemoryLayout.size = \(MemoryLayout.size)")
print("MemoryLayout.stride = \(MemoryLayout.stride)")
print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
print("MemoryLayout.size = \(MemoryLayout.size)")
print("MemoryLayout.stride = \(MemoryLayout.stride)")
print("MemoryLayout.alignment = \(MemoryLayout.alignment)")
print("MemoryLayout.size = \(MemoryLayout.size)")
print("MemoryLayout.stride = \(MemoryLayout.stride)")
print("MemoryLayout.alignment = \(MemoryLayout.alignment)")

结果:

MemoryLayout.size = 8

MemoryLayout.stride = 8

MemoryLayout.alignment = 8

MemoryLayout.size = 4

MemoryLayout.stride = 4

MemoryLayout.alignment = 4

MemoryLayout.size = 2

MemoryLayout.stride = 2

MemoryLayout.alignment = 2

MemoryLayout 是用来测定内存的

stride是步长,也就是一段连续内存空间 指定类型指针的偏移最小单位

alignment是对齐字节,一段连续内存空间,指令读取内存数据,都是标准化操作,不会出现第一个整型读了8字节,下一个整型读了4字节这样

然后我们进行 4个整型的数据的存储

首先开辟一块内存空间

UnsafeMutableRawPointer.allocate(byteCount: Int, alignment: Int)

byteCount: 开辟内存空间的总的字节大小

alignment: 连续内存空间中 每一个整型数据的对齐大小

然后存储 - UnsafeMutableRawPointer - storeBytes(of: T, as: T.Type)

of - 存储的数据

as - 存储的数据的类型

let mP = UnsafeMutableRawPointer.allocate(byteCount: 4 * MemoryLayout.size, alignment: MemoryLayout.stride)for i in 0..<4 {mP.storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {let mV = mP.load(as: Int.self)let mV = mP.load(fromByteOffset: i * MemoryLayout.stride, as: Int.self)print("mV ===> \(mV)")
}

结果

mV ===> 3

mV ===> 3

mV ===> 3

mV ===> 3

为什么不是 0, 1, 2, 3

这是因为 mP 指向 UnsafeMutableRawPointer.allocate 开辟出来的一段连续内存空间首地址

mP.load(as: Int.self) 循环里每次取的都是 从首地址处取出 数据,所以总是一样的3

调整之后

let mP = UnsafeMutableRawPointer.allocate(byteCount: 4 * MemoryLayout.size, alignment: MemoryLayout.stride)for i in 0..<4 {mP.storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {let mV = mP.load(fromByteOffset: i * MemoryLayout.stride, as: Int.self)print("mV ===> \(mV)")
}

结果

mV ===> 3

mV ===> 0

mV ===> 16

mV ===> 0

这又是为何

因为 mP.storeBytes(of: i, as: Int.self) 每次也只是往 mP指向的连续内存空间的首地址里存储,所以最后存储的 3会覆盖前面的几次写值

let mP = UnsafeMutableRawPointer.allocate(byteCount: 4 * MemoryLayout.size, alignment: MemoryLayout.stride)for i in 0..<4 {// 正解mP.advanced(by: i * MemoryLayout.stride).storeBytes(of: i, as: Int.self)
}
// 取出
for i in 0..<4 {let mV = mP.load(fromByteOffset: i * MemoryLayout.stride, as: Int.self)print("mV ===> \(mV)")
}

结果

mV ===> 0

mV ===> 1

mV ===> 2

mV ===> 3

也可以直接 计算具体指针位置进行写值,前提是必须知道指针的类型才可以

for i in 0..<4 {(mP + i * MemoryLayout.stride).storeBytes(of: i, as: Int.self)
}

size/stride/alignment的理解

情况一

struct IFLObject1 {var age: Intvar gender: Bool
}print("MemoryLayout.size = \(MemoryLayout.size)")
print("MemoryLayout.stride = \(MemoryLayout.stride)")
print("MemoryLayout.alignment = \(MemoryLayout.alignment)")

结果

MemoryLayout.size = 9

MemoryLayout.stride = 16

MemoryLayout.alignment = 8

在这里插入图片描述
alignment 对齐是内存中布局的一种方式

小单位 按照大单位去补齐, 如果凑不齐,就空余一定的空间出来,看上去就跟集中箱一样,遵循标准化原则

age占8字节 + gender占1字节, size 大小就是9字节

size 9字节 不是 age大小8字节的倍数, 空闲一部分空间,凑齐8字节整数倍,stride步长就是16字节

stride 16字节 凑齐的方式就是按照 age8字节大小去对齐,所以 alignment 就是8字节对齐

情况二

class IFLobject2 {var age: Int = 0var gender: Bool = truevar heigh: Double = 170var heigh1: Double = 170var heigh2: Double = 170var heigh3: Double = 170
}
print("MemoryLayout.size = \(MemoryLayout.size)")
print("MemoryLayout.stride = \(MemoryLayout.stride)")
print("MemoryLayout.alignment = \(MemoryLayout.alignment)")

结果

MemoryLayout.size = 8

MemoryLayout.stride = 8

MemoryLayout.alignment = 8

与结构体不同的是,struct属于值类型,栈上开辟空间,class 堆上开辟内存空间,指针大小为8字节, 所以8字节对齐,步长也是8字节

泛型指针的使用

泛型指针相比原生指针来说,就是当前指针绑定到了具体的类型

泛型指针访问过程中,并不是使用store load 方法进行存储 取值操作,而是使用到泛型指针内置的变量pointee

var age = 10
var age1 = withUnsafePointer(to: &age) {$0.pointee + 1
}
print("age1 = \(age1)")

结果

age1 = 11

另一种情况

var age = 10
withUnsafePointer(to: &age) {$0.pointee += 1
}

这种情况下 指针 $0 是不可变的,同时$0指向的内容 $0.pointee也是不可变的, 如果要操作,调整如下

var age = 10
withUnsafeMutablePointer(to: &age) {$0.pointee += 1
}
print("age = \(age)")

结果

age = 11

还有一种方式直接分配内存

var age = 10
let tPtr = UnsafeMutablePointer.allocate(capacity: 1)
tPtr.initialize(to: age)
print(tPtr.pointee)

结果

10

struct IFLObjStruct {var age: Intvar height: Double
}
var tPtr = UnsafeMutablePointer.allocate(capacity: 5)
tPtr[0] = IFLObjStruct(age: 19, height: 170.0)
tPtr[1] = IFLObjStruct(age: 20, height: 171.0)
tPtr[2] = IFLObjStruct(age: 21, height: 172.0)
tPtr[3] = IFLObjStruct(age: 22, height: 173.0)
tPtr[4] = IFLObjStruct(age: 23, height: 174.0)print(tPtr[4])

结果

IFLObjStruct(age: 23, height: 174.0)

还可以

struct IFLObjStruct {var age: Intvar height: Double
}
var tPtr = UnsafeMutablePointer.allocate(capacity: 5)
tPtr[0] = IFLObjStruct(age: 19, height: 170.0)
tPtr[1] = IFLObjStruct(age: 20, height: 171.0)
tPtr[2] = IFLObjStruct(age: 21, height: 172.0)
tPtr[3] = IFLObjStruct(age: 22, height: 173.0)
tPtr[4] = IFLObjStruct(age: 23, height: 174.0)
tPtr.deinitialize(count: 5)
// 回收内存空间
tPtr.deallocate()tPtr = UnsafeMutablePointer.allocate(capacity: 5)
for i in 0..<5 {tPtr.advanced(by: i).initialize(to: IFLObjStruct(age: 19 + i * 5, height: 170.0 + Double(i * 5)))
}
for i in 0..<5 {print(tPtr.advanced(by: i).pointee)
}

结果

IFLObjStruct(age: 19, height: 170.0)

IFLObjStruct(age: 24, height: 175.0)

IFLObjStruct(age: 29, height: 180.0)

IFLObjStruct(age: 34, height: 185.0)

IFLObjStruct(age: 39, height: 190.0)

注意:

tPtr.advanced by 参数 含义是 只需要标明移动多少个指针内存单位, 并不需要计算具体移动的内存块字节大小,

因为 泛型指针已经 指明了当前内存 绑定的具体类型, 与原生指针 adviced by 参数有所区别

一般情况下,我们会在 defer 中,也就是当前程序运行完成之后, 执行 deinitialize 与 deallocate

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...