前言

本文主要翻译今年 The Swift Programming Language (Swift 4) 中新出的章节 -《Memory Safety》。在 Swift 4 中,内存安全访问进行很大的优化(《What's New in Swift 4 ?》)。

默认情况下,Swift 会克服代码层面上的一些不安全的行为,如:确保一个变量被初始化完后才能被访问、确保变量在销毁后不会被访问等等安全操作。

Swift 也会确保在多路访问内存中同一区域时不会冲突(独占访问该区域)。通常情况下,我们完全无需考虑内存访问冲突的问题,因为 Swift 是自动管理内存的。然而,在码代码的时候,了解那些地方可能发生内存访问冲突是非常重要的。通常情况下,如果你的代码有内存访问冲突,那么 Xcode 会提示编译错误或者运行时错误。

本文不会介绍什么是内存访问冲突。详见 The Swift Programming Language (Swift 4)。如果你写的是并发或者多线程的程序,内存冲突访问与单线程是非常相似的一个问题。本文主要讨论单线程上的内存冲突访问。如果想检测多线程是否存在内存访问冲突,你可以看看这篇文档。

我们可以把访问分为两种:即时和长期(instantaneous & long-term)

  • 即时访问:即在访问开始至结束前都不可能有其他代码来访问同一区域。
  • 长期访问:即在访问开始至结束前可能有其他代码来访问同一区域。长期访问可能和其他即时访问或者长期访问重叠。

重叠访问主要带有 in-out 参数的函数(或方法)以及结构体中带有 mutating 关键字的方法。我们下面来看看例子。

In-Out 参数的访问冲突

一个函数对其 in-out 参数具有长期的访问权限,如下代码:

Excerpt From: Apple Inc. "The Swift Programming Language (Swift 4).” iBooks".


var stepSize = 1 func increment(_ number: inout Int) {
number += stepSize
} increment(&stepSize)
// Error: conflicting accesses to stepSize

在上述代码中,stepSize 是一个全局变量,而且被作为一个 in-out 参数传给 increment(_:) 方法。冲突的原因在于 number 和 stepSize 引用的是内存中同一区域,并且同时进行读写访问,因此导致访问冲突。

我们可以采用复制 stepSize 的方式解决该问题:


// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize) // Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
// stepSize is now 2

self 的访问冲突

在结构体中,带有 mutating 关键字的方法调用期间对 self 具有写入权限。


extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
} var oscar = Player(name: "Oscar", health: 10, energy: 10) var maria = Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // OK

上述代码是 Ok 的,即时写入权限在时间上是重叠的,但是是分别访问 oscar 的 health 和 maria 的 health,因此在 shareHealth(with:) 方法中并没有发生内存访问冲突。

然而,如果你把 oscar 作为参数传给 shareHealth(with:),那么就会产生内存访问冲突:


oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar

很显然,shareHealth(with:) 方法中的 self 和 teammate 同时指向内存中同一区域,即同时对 oscar 的 health 进行读写访问,因此导致访问冲突。

属性的访问冲突

像结构体、元组、枚举这些类型都是由各个值组成的,如:结构体的各种属性、元组的各种元素等等。因为它们都是值类型,这意味着对其中一个属性的读写访问就是对整个值进行读写访问。代码如下:


var playerInformation = (health: 10, energy: 20) balance(&playerInformation.health, &playerInformation.energy) // Error: conflicting access to properties of playerInformation

上述代码不难理解,因为元祖是值类型,上述 balance(_:_:) 发生内存访问冲突,即同时访问 playerInformation。

下面我们再看一下结构体,其中 holly 是一个全局变量


var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error

上述代码会报这样一个错误:Simultaneous accesses to 0x10****580, but modification requires exclusive access。其实就是内存访问冲突了,Swift 4 中也针对这块做了优化处理,感兴趣的同学可以查阅我之前写的一篇文章《[WWDC17] What's New in Swift 4 ?》。

在实践中,上述代码中的 holly 一般是个局部变量而非全局变量,编译器可以保证对结构体的存储属性进行重叠访问是安全的,代码如下:


func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}

上述代码运行是 Ok 的,有时候,限制结构体的各属性进行重叠访问是没有必要的,这也就是为什么 someFunction() 没有发生冲突访问的原因。内存访问安全虽应当得到保证,但是独占访问比内存安全访问要求更加严格,从上述代码可看出,即时违背了独占访问的原则,内存安全也能得到保证。一般情况下,编译器会在如下条件下保证对结构体的存储属性进行安全的重叠访问:

  • 只访问某个实例的存储属性,而不是计算属性或类属性
  • 访问的是局部的结构体变量,而不是全局变量
  • 结构体没有被任何闭包所捕获,或者仅被非逃逸闭包捕获。

感兴趣的同学可以查阅这里 The Swift Programming Language (Swift 4)。

关于 Swift 4 中内存安全访问的更多相关文章

  1. 浅谈 Swift 2 中的 Objective-C 指针

    浅谈 Swift 2 中的 Objective-C 指针 2015-09-07  499 文章目录 1. 在 Swift 中读 C 指针 2. 在 Swift 中创建 C 指针 3. 总结 作者:Ja ...

  2. java中内存分配策略及堆和栈的比较

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...

  3. 深入理解Linux中内存管理

    前一段时间看了<深入理解Linux内核>对其中的内存管理部分花了不少时间,但是还是有很多问题不是很清楚,最近又花了一些时间复习了一下,在这里记录下自己的理解和对Linux中内存管理的一些看 ...

  4. C语言中内存对齐方式

    一.什么是对齐,以及为什么要对齐: 1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问, ...

  5. MySQL中内存分为全局内存和线程内存

    首先我们来看一个公式,MySQL中内存分为全局内存和线程内存两大部分(其实并不全部,只是影响比较大的 部分): 复制代码 代码如下: per_thread_buffers=(read_buffer_s ...

  6. IOS中内存管理机制浅解

    我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是 由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得 ...

  7. C语言中内存分配 (转)

    在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要介绍内存管理基本概念,重 ...

  8. LoadTest中内存和线程Troubleshooting实战

    LoadTest中内存和线程Troubleshooting实战 在端午节放假的三天中,我对正在开发的Service进行了LoadTest,尝试在增大压力的条件下发现问题. 该Service为独立进程的 ...

  9. Java虚拟中内存分块

    Java虚拟机JVM(Java Virtual Machine)中内存分块 JAVA中通常分为5个区域虚拟机栈.堆.方法区.程序计数器.本地方法区.我们一般讲的是Java虚拟机管理的四个区域虚拟机栈. ...

随机推荐

  1. 框架应用:Spring framework (二) - AOP技术

    基础概念 线程中的方法栈 java程序虚拟机启动时会载入程序码,虚拟机会为每一条正在运行的线程生成一个方法调用栈,线程以方法运行为执行单位. AOP概念以及目标 AOP是面向切面编程,其实就是在不修改 ...

  2. [UIKit学习]07.关于如何选择UIButton、UILable、UIImageView

    如何选择UIButton.UILable.UIImageView 在不添加手势的前提下,只要不涉及到点击和多状态表现就尽量不要选择UIButton

  3. Flask-WTF 配置、验证及日志P4

    参数配置 参数 说明 WTF_CSRF_ENABLED 设置为False以禁用所有CSRF保护 WTF_CSRF_CHECK_DEFAULT 使用CSRF保护扩展时,这可以控制每个视图是否受到默认保护 ...

  4. 为异常处理做准备,熟悉一下WinDbg工具

    为异常处理做准备,熟悉一下WinDbg工具 马上开始异常处理第二讲,但是在讲解之前,还有熟悉一下我们的WinDbg工具,当然你如果熟悉这个工具,那么就可以不用看了. 一丶熟悉WinDbg界面 刚开始打 ...

  5. [js高手之路] html5 canvas系列教程 - 线条样式(lineWidth,lineCap,lineJoin,setLineDash)

    上文,写完弧度与贝塞尔曲线[js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具),本文主要是关于线条的样式设置 lineWidth: 设置线条的宽 ...

  6. ubuntu12.04添加程序启动器到Dash Home

    ubuntu12.04 dash home中每个图标对应/usr/share/applications当中的一个配置文件(文件名后缀为.desktop).所以要在dash home中添加一个自定义程序 ...

  7. C#进阶之AOP

    一.AOP概念(转自) AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技 ...

  8. Ubuntu 16.04源码编译安装nginx 1.10.0

    一.下载相关的依赖库 pcre 下载地址 http://120.52.73.43/jaist.dl.sourceforge.net/project/pcre/pcre/8.38/pcre-8.38.t ...

  9. 【Windows 10 应用开发】细说文本资源文件(resw)

    最近,小戏骨版<红楼梦>很是火热,老周一口气看完了9集,一直看到 Surface 的风扇呼呼响.林黛玉和薛宝钗这两个角色都演得不怎么样,倒是演史湘云的娃娃演得不错,老周甚是喜欢. 于是,昨 ...

  10. less使用ch1--认识语法

    @charset "utf-8"; //注释------------------------------ /*我是可以被编译出来的*/ //不能被编译出来 //变量-------- ...