下一站“内存”:成为《黑客帝国》中的尼奥并不难

存储 存储软件
Swift是一种内存安全语言。它限制用户直接访问内存,确保用户在使用内存前已初始化所有内容。不安全的Swift API允许用户通过指示器直接访问内存。

本文转载自公众号“读芯术”(ID:AI_Discovery)

你有没有想过,尼奥深陷“母体”时是如何设法改变它的?

他又是如何把子弹从崔妮蒂身上清除的?显然,“母体”只是机器编写的一个程序,尼奥能在程序运行时更改程序的二进制代码,并在矩阵中交换子弹的位置。

如果说,你们也可以这么做,也可以成为你程序中的尼奥,你会作何感想?我的意思是,或许各位很难与电影里的尼奥相匹敌,不过也差不多了。

程序运行过程中如何访问和更改内存?使用Swift的不安全API就可以做到。

[[328712]]

什么是不安全?

Swift是一种内存安全语言。它限制用户直接访问内存,确保用户在使用内存前已初始化所有内容。不安全的Swift API允许用户通过指示器直接访问内存。

或许不安全这个词听起来很糟糕,不过它并不意味着用户代码处于危险状态且无法正常运行。Swift可以确保用户不犯明显错误。而使用不安全的API时,用户必须时刻注意代码的运行情况。尤其是在使用C、C++等语言时,这些API十分有用。

[[328713]]

图源:unsplash

在弄清楚什么是不安全的Swift之前,需要先弄清楚什么是安全。

什么是内存安全?

想弄清楚这种情况,先来看几个例子。

例1:使用年龄数组,尝试在数组的第一个元素中加1。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

可以看到,这会出现错误,该值应与前面字符隔开。继续尝试。

这样好像可以了。如果用空数组再试一次呢?

下一站“内存”:成为《黑客帝国》中的尼奥并不难

它崩溃了......再试试别的。

例2:尝试查找年龄数组的平均值。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

它运行良好,就好像被施了魔法。不过空数组也能行吗?

下一站“内存”:成为《黑客帝国》中的尼奥并不难

它又崩溃了......这次我们将试着访问数组中的元素。

例3:尝试访问数组第3个和第4个索引处的元素。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

访问第3个索引时,它可以正常运行,但访问第4个索引时,它又双崩溃了。

很明显,如果尝试任何异常操作,那么程序每次都会崩溃。如果崩溃是安全的,那......什么是不安全的?

想一下,假如你尝试访问数组中的年龄,而程序返回了一个负值,这种情况是不可能同时发生吧?可如果你尝试获取账户余额,程序返回的值是1000,而实际余额有2000,那该怎么办?

没错,意外行为要危险得多。Swift提供了安全的API,从而让用户避免意外行为。深入了解不安全的API之前,先来看看内存和内存布局。

什么是内存?

在计算机中,内存以数字形式存储,比如许多的“1”和“0”,我们称之为比特。如果将这样的内存可视化,会得到下面的图像。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

二进制代码

上图呈现的是连续的比特流,代表实际数据。如果将每8个比特分为一组,那么这些比特组就是字节。如果将这些字节可视化,它们将如下图所示。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

字节代码

为便于理解,把它们转换成十六进制代码。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

十六进制代码

如果继续将每8个十六进制代码分为一组,就会得到8字节或者是64比特的字。这也是当今全球使用的通用格式,构成了大部分设备的“64位系统”。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

字(64比特)

每个字都关联一个地址,该地址也是十六进制数。每个内存地址之间都存在8个字节的差值,该差值刚好等于字的大小。该地址可用于访问内存中该点的数据。

带有内存地址的字

什么是内存布局?

这是一个Swift API,可在运行时告知用户所提供类型的大小、对齐方式和跨度。

  • 大小:该类型所需的字节数。
  • 对齐方式:内存应是对齐方式的倍数。
  • 跨度:两个元素之间的距离。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

内存布局Swift

尝试一些代码,以进一步了解内存布局API。这些是在64位操作系统计算机上运行该代码所得到的值。

  1. MemoryLayout<Int>.size // returns 8 
  2. MemoryLayout<Int>.alignment // returns 8 
  3. MemoryLayout<Int>.stride // returns 8 
  4. MemoryLayout<Bool>.size // returns 1 
  5. MemoryLayout<Bool>.alignment // returns 1 
  6. MemoryLayout<Bool>.stride // returns 1 
  7. MemoryLayout<Double>.size // returns 8 
  8. MemoryLayout<Double>.alignment // returns 8 
  9. MemoryLayout<Double>.stride // returns 8 

什么是不安全的指示器?

不安全的指示器是Swift API的其中一种,它允许用户访问流中的数据或将数据与特定类型(如Int、Double等)绑定。与直接内存一起使用的类型,获取“不安全”前缀。

下一站“内存”:成为《黑客帝国》中的尼奥并不难

Swift提供了8种类型的不安全指示器API,可根据实现特定目标的需要进行使用。

  • UnsafePointer
  • UnsafeMutablePointer
  • UnsafeRawPointer
  • UnsafeMutableRawPointer
  • UnsafeBufferPointer
  • UnsafeMutableBufferPointer
  • UnsafeRawBufferPointer
  • UnsafeMutableRawBufferPointer

为了更好地理解,来看一些例子。

例1:原始指示器

  1. let count = 2 
  2. let stride = MemoryLayout<Int>.stride 
  3. let alignment = MemoryLayout<Int>.alignment 
  4. let byteCount = stride * count //total number of byteslet pointer =UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment:alignment)defer { 
  5.   pointer.deallocate() 
  6. }pointer.storeBytes(of: 30, as: Int.self 
  7. pointer.advanced(by: stride).storeBytes(of: 3, as: Int.self) 
  8. pointer.load(as: Int.self) 
  9. pointer.advanced(by: stride).load(as: Int.self)let bufferPointer =UnsafeRawBufferPointer(start: pointer, count: byteCount)for (index, byte) inbufferPointer.enumerated() { 
  10.   print("byte \(index) ->\(byte)") 
  11. }// byte 0 -> 30 
  12. // byte 1 -> 0 
  13. // byte 2 -> 0 
  14. // byte 3 -> 0 
  15. // byte 4 -> 0 
  16. // byte 5 -> 0 
  17. // byte 6 -> 0 
  18. // byte 7 -> 0 
  19. // byte 8 -> 3 
  20. // byte 9 -> 0 
  21. // byte 10 -> 0 
  22. // byte 11 -> 0 
  23. // byte 12 -> 0 
  24. // byte 13 -> 0 
  25. // byte 14 -> 0 
  26. // byte 15 -> 0 
  • advanced用于按提供的跨度移动指示器。
  • UnsafeMutableRawPointer.allocate通过分配所需的类型返回可变的指示器。
  • UnsafeRawBufferPointer让用户以字节集合的方式访问内存。用户可对其进行迭代编辑来访问字节。
  • storeByte会将提供的字节存储在指定内存中,而load将通过与特定类型(此处为Int)绑定来加载数据。
  • ARC无法使用该API,用户必须自行重新分配,因此,需要延迟代码块。每当指令从当前代码块返回时,它都将重新分配指示器。

例2:类型化的指示器

  1. let count = 2 
  2. let stride = MemoryLayout<Int>.stridelet pointer =UnsafeMutablePointer<Int>.allocate(capacity: count) 
  3. pointer.initialize(repeating: 0, count: count)defer { 
  4.   pointer.deinitialize(count: count) 
  5.   pointer.deallocate() 
  6. }pointer.pointee = 42 
  7. pointer.advanced(by: 1).pointee = 6let bufferPointer =UnsafeBufferPointer(start: pointer, count: count)for (index, value) inbufferPointer.enumerated() { 
  8.   print("value \(index) ->\(value)") 
  9. }// value 0 -> 42 
  10. // value 1 -> 6 
  • UnsafeMutablePointer.allocate为提供的计数分配T类型所需的字节数。
  • initialize将使用提供的值初始化指示器。
  • pointee可用于存储、加载T类型的值。
  • advanced将指示器移至下一个字节。

不要做什么?

使用不安全的API时:

  • 一次只绑定一种类型(尝试临时绑定)
    1. let count = 3 
    2. let stride = MemoryLayout<Int16>.stride 
    3. let alignment = MemoryLayout<Int16>.alignment 
    4. let byteCount = count * stridelet pointer = UnsafeMutableRawPointer.allocate( 
    5.  byteCount: byteCount, 
    6.   alignment: alignment)let typedPointer1pointer.bindMemory(to: UInt16.self, capacity: count)// 911, someone isbreaking the Law 
    7. let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)//Try this way instead 
    8. typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) { 
    9.   (boolPointer:UnsafeMutablePointer<Bool>) in 
    10.   print(boolPointer.pointee) 
  • 不要从withUnsafeBytes返回指示器(这样做将来可能出现故障)
    1. struct ExampleStruct { 
    2.   let number: Int 
    3.   let flag: Bool 
    4. }var exampleStruct = ExampleStruct(number: 25, flag: true)let bytes = withUnsafeBytes(of:&exampleStruct) { bytes in 
    5.   return bytes // It may cause strangebugs anytime 
    6. }print("Here are are bytes to ruin your life", bytes) 
  • 不要盲目相信代码(在代码块末尾检查数据)
    1. let count = 3 
    2. let stride = MemoryLayout<Int16>.stride 
    3. let alignment = MemoryLayout<Int16>.alignment 
    4. let byteCount =  count * strideletpointer = UnsafeMutableRawPointer.allocate( 
    5.   byteCount: byteCount, 
    6.   alignment: alignment)let bufferPointerUnsafeRawBufferPointer(start: pointer, count: byteCount + 1) 
    7. // Putting it intentionally to cause an issue :pfor byte in bufferPointer { 
    8.   print(byte) // Check each byte 

学会这一招,快去你的程序中“遨游”吧!

 

责任编辑:赵宁宁 来源: 读芯术
相关推荐

2011-09-05 17:11:51

2013-12-23 09:31:37

2015-12-10 09:47:37

2015-05-29 09:56:11

慧聪电子网

2009-01-16 22:37:44

2020-05-26 19:31:09

人工智能AI实时服务

2016-01-18 10:40:04

VRMR

2013-09-25 09:58:17

虚拟化网络

2012-02-07 09:25:43

移动市场Facebook

2022-11-24 08:00:00

2010-09-20 11:39:50

2013-05-29 09:51:16

BYODBYOD管理IT应用

2014-07-07 09:24:04

2020-03-11 15:00:46

AI人工智能智能

2015-08-20 10:32:32

2009-08-28 17:58:16

2013-03-08 10:03:36

2010-08-11 11:40:06

云计算

2019-06-22 16:03:28

托管云计算企业

2014-01-10 16:33:17

点赞
收藏

51CTO技术栈公众号