全文共字,预计学习时长15分钟
图源:animationxpress你有没有想过,尼奥深陷“母体”时是如何设法改变它的?
他又是如何把子弹从崔妮蒂身上清除的?显然,“母体”只是机器编写的一个程序,尼奥能在程序运行时更改程序的二进制代码,并在矩阵中交换子弹的位置。
如果说,你们也可以这么做,也可以成为你程序中的尼奥,你会作何感想?我的意思是,或许各位很难与电影里的尼奥相匹敌,不过也差不多了。
程序运行过程中如何访问和更改内存?使用Swift的不安全API就可以做到。
什么是不安全?
Swift是一种内存安全语言。它限制用户直接访问内存,确保用户在使用内存前已初始化所有内容。不安全的SwiftAPI允许用户通过指示器直接访问内存。
或许不安全这个词听起来很糟糕,不过它并不意味着用户代码处于危险状态且无法正常运行。Swift可以确保用户不犯明显错误。而使用不安全的API时,用户必须时刻注意代码的运行情况。尤其是在使用C、C++等语言时,这些API十分有用。
图源:unsplash在弄清楚什么是不安全的Swift之前,需要先弄清楚什么是安全。
什么是内存安全?
想弄清楚这种情况,先来看几个例子。
例1:使用年龄数组,尝试在数组的第一个元素中加1。
可以看到,这会出现错误,该值应与前面字符隔开。继续尝试。
这样好像可以了。如果用空数组再试一次呢?
它崩溃了......再试试别的。
例2:尝试查找年龄数组的平均值。
它运行良好,就好像被施了魔法。不过空数组也能行吗?
它又崩溃了......这次我们将试着访问数组中的元素。
例3:尝试访问数组第3个和第4个索引处的元素。
访问第3个索引时,它可以正常运行,但访问第4个索引时,它又双崩溃了。
很明显,如果尝试任何异常操作,那么程序每次都会崩溃。如果崩溃是安全的,那......什么是不安全的?
想一下,假如你尝试访问数组中的年龄,而程序返回了一个负值,这种情况是不可能同时发生吧?可如果你尝试获取账户余额,程序返回的值是,而实际余额有,那该怎么办?
没错,意外行为要危险得多。Swift提供了安全的API,从而让用户避免意外行为。深入了解不安全的API之前,先来看看内存和内存布局。
什么是内存?
在计算机中,内存以数字形式存储,比如许多的“1”和“0”,我们称之为比特。如果将这样的内存可视化,会得到下面的图像。
二进制代码上图呈现的是连续的比特流,代表实际数据。如果将每8个比特分为一组,那么这些比特组就是字节。如果将这些字节可视化,它们将如下图所示。
字节代码为便于理解,把它们转换成十六进制代码。
十六进制代码如果继续将每8个十六进制代码分为一组,就会得到8字节或者是64比特的字。这也是当今全球使用的通用格式,构成了大部分设备的“64位系统”。
字(64比特)每个字都关联一个地址,该地址也是十六进制数。每个内存地址之间都存在8个字节的差值,该差值刚好等于字的大小。该地址可用于访问内存中该点的数据。
带有内存地址的字什么是内存布局?
这是一个SwiftAPI,可在运行时告知用户所提供类型的大小、对齐方式和跨度。
·大小:该类型所需的字节数。
·对齐方式:内存应是对齐方式的倍数。
·跨度:两个元素之间的距离。
内存布局Swift尝试一些代码,以进一步了解内存布局API。这些是在64位操作系统计算机上运行该代码所得到的值。
MemoryLayoutInt.size//returns8
MemoryLayoutInt.alignment//returns8
MemoryLayoutInt.stride//returns8
MemoryLayoutBool.size//returns1
MemoryLayoutBool.alignment//returns1
MemoryLayoutBool.stride//returns1
MemoryLayoutDouble.size//returns8
MemoryLayoutDouble.alignment//returns8
MemoryLayoutDouble.stride//returns8
什么是不安全的指示器?
不安全的指示器是SwiftAPI的其中一种,它允许用户访问流中的数据或将数据与特定类型(如Int、Double等)绑定。与直接内存一起使用的类型,获取“不安全”前缀。
Swift提供了8种类型的不安全指示器API,可根据实现特定目标的需要进行使用。
1.UnsafePointerT
2.UnsafeMutablePointerT
3.UnsafeRawPointer
4.UnsafeMutableRawPointer
5.UnsafeBufferPointerT
6.UnsafeMutableBufferPointerT
7.UnsafeRawBufferPointer
8.UnsafeMutableRawBufferPointer
为了更好地理解,来看一些例子。
例1:原始指示器
letcount=2
letstride=MemoryLayoutInt.stride
letalignment=MemoryLayoutInt.alignment
letbyteCount=stride*count//totalnumberofbytesletpointer=UnsafeMutableRawPointer.allocate(byteCount:byteCount,alignment:alignment)defer{
pointer.deallocate()
}pointer.storeBytes(of:30,as:Int.self
pointer.advanced(by:stride).storeBytes(of:3,as:Int.self)
pointer.load(as:Int.self)
pointer.advanced(by:stride).load(as:Int.self)letbufferPointer=UnsafeRawBufferPointer(start:pointer,count:byteCount)for(index,byte)inbufferPointer.enumerated(){
print(byte\(index)-\(byte))
}//byte0-30
//byte1-0
//byte2-0
//byte3-0
//byte4-0
//byte5-0
//byte6-0
//byte7-0
//byte8-3
//byte9-0
//byte10-0
//byte11-0
//byte12-0
//byte13-0
//byte14-0
//byte15-0
·advanced用于按提供的跨度移动指示器。
·UnsafeMutableRawPointer.allocate通过分配所需的类型返回可变的指示器。
·UnsafeRawBufferPointer让用户以字节集合的方式访问内存。用户可对其进行迭代编辑来访问字节。
·storeByte会将提供的字节存储在指定内存中,而load将通过与特定类型(此处为Int)绑定来加载数据。
·ARC无法使用该API,用户必须自行重新分配,因此,需要延迟代码块。每当指令从当前代码块返回时,它都将重新分配指示器。
图源:unsplash例2:类型化的指示器
letcount=2
letstride=MemoryLayoutInt.strideletpointer=UnsafeMutablePointerInt.allocate(capacity:count)
pointer.initialize(repeating:0,count:count)defer{
pointer.deinitialize(count:count)
pointer.deallocate()
}pointer.pointee=42
pointer.advanced(by:1).pointee=6letbufferPointer=UnsafeBufferPointer(start:pointer,count:count)for(index,value)inbufferPointer.enumerated(){
print(value\(index)-\(value))
}//value0-42
//value1-6
·UnsafeMutablePointerT.allocate为提供的计数分配T类型所需的字节数。
·initialize将使用提供的值初始化指示器。
·pointee可用于存储、加载T类型的值。
·advanced将指示器移至下一个字节。
不要做什么?
使用不安全的API时:
·一次只绑定一种类型(尝试临时绑定)
letcount=3
letstride=MemoryLayoutInt16.stride
letalignment=MemoryLayoutInt16.alignment
letbyteCount=count*strideletpointer=UnsafeMutableRawPointer.allocate(
byteCount:byteCount,
alignment:alignment)lettypedPointer1=pointer.bindMemory(to:UInt16.self,capacity:count)//,someoneisbreakingtheLaw
lettypedPointer2=pointer.bindMemory(to:Bool.self,capacity:count*2)//Trythiswayinstead
typedPointer1.withMemoryRebound(to:Bool.self,capacity:count*2){
(boolPointer:UnsafeMutablePointerBool)in
print(boolPointer.pointee)
}
·不要从withUnsafeBytes返回指示器(这样做将来可能出现故障)
structExampleStruct{
letnumber:Int
letflag:Bool
}varexampleStruct=ExampleStruct(number:25,flag:true)letbytes=withUnsafeBytes(of:exampleStruct){bytesin
returnbytes//Itmaycausestrangebugsanytime
}print(Herearearebytestoruinyourlife,bytes)
·不要盲目相信代码(在代码块末尾检查数据)
letcount=3
letstride=MemoryLayoutInt16.stride
letalignment=MemoryLayoutInt16.alignment
letbyteCount=count*strideletpointer=UnsafeMutableRawPointer.allocate(
byteCount:byteCount,
alignment:alignment)letbufferPointer=UnsafeRawBufferPointer(start:pointer,count:byteCount+1)
//Puttingitintentionallytocauseanissue:pforbyteinbufferPointer{
print(byte)//Checkeachbyte
}
学会这一招,快去你的程序中“遨游”吧!
留言点赞