Swift之深入解析内存安全

news2024/9/27 23:33:12

一、内存安全

  • 一般来说,Swift 会阻止代码中的不安全行为。例如,Swift 会保证变量在被使用前已经初始化,在释放某变量后其内存也会变得不可访问,以及检查数组索引是否存在越界错误。
  • Swift 还通过要求修改内存中位置的代码具有对该内存的独占访问权,来确保对同一内存区域的多重访问不会产生冲突。由于 Swift 会自动管理内存,因此大多数时候根本不需要考虑内存访问的问题。然而,如果了解什么地方会有潜在的内存冲突发生也是很重要,这样就可以避免写出对内存访问有冲突的代码,如果代码中确实包含冲突,则会出现编译时错误或运行时错误。

二、内存的访问冲突

  • 当执行设置变量的值、将参数传递给函数之类的代码时,访问内存这件事情会就发生。
  • 如下所示的代码包含一个读取操作和一个访问操作:
// A write access to the memory where one is stored.
var one = 1

// A read access from the memory where one is stored.
print("We're number \(one)!")
  • 当不同部分的代码试图同时访问同一块内存时,可能会发生内存冲突访问。同时访问同一块内存可能会导致不可预测或不一致的行为。在 Swift 中,有多种方法可以实现在跨越好几行代码的过程下修改某个值,这导致可以实现在修改自身的过程中去尝试访问自己的值。
  • 可以通过一个相似的问题来更好地帮助我们来理解这种冲突,例如现在要在一张纸上更新购物预算清单,更新这张预算清单分为两个步骤:
    • 需要添加商品的名称和价格;
    • 需要更改总价来匹配更新后的账单,在这个更新步骤的前后,都可以从账单中正确的读取任何数据;
  • 如下图所示:

在这里插入图片描述

  • 当往清单中添加商品时,清单处于一个临时的、无效的状态,因为这时总价还没有被更新、还不能反映那些新加的商品,因此当在添加商品的过程中,读取总价格的话,会给出一个错误的答案。
  • 这个例子同样也展示了在解决冲突访问时你可能会遇到的问题:不一样解决冲突方式会带来不一样的答案,要知道哪个答案是正确的通常来说没有那么显而易见。在这个例子中,主要看你是想要原来的总价格还是更新后的总价格,5 和 5 和 5 和 320 都可能是正确答案,也就是说,在解决冲突访问之前,得先要搞清楚需要的是什么。
  • 注意:
    • 如果在编写有关并发或多线程的代码,那么内存访问冲突可能是一个常见的问题,但要注意的是,在这讨论的冲突访问是可能发生在单线程上,并且不涉及并发或多线程代码。
    • 如果在单线程中对内存的访问存在冲突,Swift 会确保在编译时或运行时报错。对于多线程代码,可以使用 Thread Sanitizer 来检测多线程的冲突访问。

三、冲突访问的特征

  • 在冲突访问的时候,有三个访问的特征值得注意:
    • 该访问操作是读还是写;
    • 访问的时常;
    • 具体访问的位置。
  • 具体来说,如果有两个满足了以下所有条件的访问操作,那么它们是会发生冲突的:
    • 它们之中至少一个是写入操作或非原子(nonatomic)操作;
    • 它们访问了内存中的相同位置;
    • 它们的持续时间是有重叠的。
  • 通常来说,一个读取访问和一个写入访问的区别是很明显的:一个写入访问会改变内存中的位置,但读取访问不会。内存中的位置是指要访问的内容,例如:变量、常量或属性,内存访问可以是瞬时的,也可以是维持一段时间的。如果一个操作仅使用了 C 原子(atomic)操作,则该操作是原子操作,否则就是非原子的;如果某个访问在开始之后和结束之前都无法运行其他代码,那么这个访问就是一个瞬时访问,从本质上来说,两个瞬时访问是不能在同一时间发生的,并且大多数内存访问操作都是瞬时的。
  • 如下所示的代码中读取和写入访问都是瞬时的:

func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
  • 然而,还有几种访问内存的方式,被称作长期访问(long-term accesses),这种访问方式会涵盖其他代码的执行时机。瞬时访问和长期访问之间的区别在于,其他代码可以在一个长期访问期间(开始之后至结束之前)运行,这就叫重叠(overlap)。长期访问可以与其他长期访问重叠,也可以和瞬时访问重叠。重叠访问主要出现在用了 in-out 参数的函数和方法中、或是出现在结构体的 mutating 方法中。

四、 In-Out 参数的访问冲突

  • 一个函数对其所有 in-out 参数具有长期写入访问(long-term write access)的能力,In-out 参数的写入访问是等所有非 in-out 参数被评估 (?) 之后才开始,并且将持续该函数调用的整个过程。如果有多个 in-out 参数,则写入访问的开始顺序与参数出现的顺序相同。
  • 使用这种长期写入访问的一个后果是,不可以访问以 in-out 形式传递的原始变量(即使从范围规则和访问控制的角度来说这样是允许的),任何对原始变量的访问都会导致冲突的发生。如下所示:
var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&stepSize)
// Error: conflicting accesses to stepSize
  • 在上面的代码中,stepSize 是一个全局变量,正常来说可以在 increment(_😃 内访问它。然而,对 stepSize 的读取访问和对 number 的写入访问重叠。再如下图所示,number 和 stepSize 都指向内存中的同一位置, 读取和写入访问引用相同的内存,并且它们重叠,从而产生了冲突:

在这里插入图片描述

  • 解决这种冲突的一个办法是显式复制 stepSize:
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)

// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
  • 当在调用 increment(_:) 之前就复制 stepSize 的话,很明显 copyOfStepSize 会在当前基础上增加,读取访问在写入访问开始之前结束,因此没有冲突。另一个对 in-out 函数使用长期访问会产生的问题是,当将单个变量作为同一函数的多个 in-out 参数来传递时,会产生冲突。如下所示:
func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore)  
balance(&playerOneScore, &playerOneScore)
// Error: conflicting accesses to playerOneScore
  • 上述 balance(_:_:) 函数的作用是修改两个参数的值以让他们平均分配,使用 playerOneScore 和 playerTwoScore 作为参数时不会产生冲突(虽然它们有两个时间重叠的写入访问,但是他们访问的是内存中的不同位置)。相反,将 playerOneScore 作为两个参数的值传递会产生冲突,因为它试图同时对内存中的同一位置执行两次写入访问。
  • 注意:因为运算符也是函数,所以它们也可以进行带有 in-out 参数的长期访问。例如,如果 balance(_:_:) 是名为 <^> 的运算符,则编写 playerOneScore <^> playerOneScore 将产生与 balance(&playerOneScore, &playerOneScore) 一样的冲突错误。

五、在函数中访问自身导致的冲突

  • 一个结构体中的 mutating 方法被调用期间,是可以对它的 self 进行写入访问的。例如,有一个游戏中,每个玩家受伤时健康值会减少,在用技能时能量值会减少:
struct Player {
    var name: String
    var health: Int
    var energy: Int

    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}
  • 在上面的 restoreHealth() 方法中,对 self 的写入访问从该方法的开头开始,一直持续到该方法返回为止。在这种情况下,restoreHealth() 中没有其他代码可以重叠访问 Player 实例的属性。如下的 shareHealth(with:) 方法将另一个 Player 实例用作 in-out 参数,从而可能导致访问重叠:
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
  • 在上面的示例中,调用 Oscar 与 Maria 共享生命值的 shareHealth(with:) 方法不会引起冲突。在方法调用过程中,对 oscar 有写入访问,因为 oscar 是 mutating 方法中 self 的值,并且与 maria 的写入访问的持续时间是一致的,因为 maria 是作为 in-out 参数传递的。如下所示,可以看到它们访问内存中的不同位置,因此即使两个写访问在时间上重叠,也不会冲突:

在这里插入图片描述

  • 但是,如果传递一个 oscar 作为 shareHealth(with:) 的参数,就会产生冲突:
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
  • 这个 mutating 方法需要在方法持续时间内对 self 进行写入访问,而 in-out 参数需要在相同持续时间内对 teammate 进行写入访问。在该方法中,自己和队友都指向内存中的同一位置,如下所示,这两个写入访问引用相同的内存,并且它们重叠,从而产生了冲突:

在这里插入图片描述

六、访问属性时的冲突

  • 类似于结构体、枚举和元组这些类型都是由堵路的组合值组成的,例如结构体的属性,或者是元组的元素。因为这些都是值类型,所以对值类型的任何部分的修改都会使整个值发生更改,这意味着对某一个属性的读取或者写入操作是需要去对整个值读取或者写入。例如,对元组元素的重叠写入访问会产生冲突:
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
  • 可以看到,对元组的元素调用 blance(_:_) 会产生冲突,这是因为他们在对 playerInformation 的写入访问存在重叠。playerInformation.health 和 playerInformation.energy 都是作为 in-out 参数被传入的,这意味着 balance(_:_:) 需要在函数调用期间对其进行写入访问。在这两种情况下,对元组元素的写入访问都需要对整个元组区进行写入访问,那就是说有两个对 playerInformation 的写入访问,并且持续时间重叠,从而导致冲突。
  • 如下所示,展示了一个类似的错误,出现在对一个全局变量结构体的属性进行重叠写入访问:
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // Error
  • 事实上,对结构属性的大多数重叠访问都是安全的。例如,如果在上面的示例中将变量 holly 更改为局部变量而不是全局变量,则编译器是正常工作的,证明了对结构体的存储属性的重叠访问是安全的:
func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // OK
}
  • Oscar 的 health 和 energy 作为两个 in-out 参数传递给了 balance(_:_:),编译器可以证明这样是内存安全的,这两个存储的属性不会以任何方式交互。
  • 在保护内存安全时,限制结构体属性的重复访问并非是必须的。内存安全是理想的保证,但是独占访问是一个比内存安全更严格的要求,这意味着即使有一些代码违反了独占访问的要求,它也可以是符合内存安全的要求的。如果编译器可以证明对内存的非独占访问仍然是安全的,则 Swift 允许使用这种仅做到了内存安全的代码。
  • 特别指出,如果满足以下条件,那就可以证明重叠访问某结构体的属性是安全的:
    • 只访问了实例的存储属性,而不是计算属性或类属性;
    • 这个结构体是局部变量而不是全局变量;
    • 这个结构体要么没有被任何闭包捕获,要么只被非逃逸闭包捕获。
  • 如果编译器无法证明这个访问是安全的,则它是不被允许进行访问的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/438907.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通达信缠论顶底分型选股公式(一笔优化版)

在前文《缠论底分型选股公式&#xff0c;处理了包含关系》中介绍了缠论底分型&#xff0c;并编写了选股公式。底分型条件比较容易满足&#xff0c;因此产生的信号比较多。有热心网友提出&#xff0c;可以用顶底分型构成一笔过滤信号。 缠论一笔的构成条件&#xff1a;两个相邻…

砷化镓,锑化铟的能带结构

目录 回顾 正课 1.锑化铟的能带结构 2.砷化镓的能带结构 3.混合晶体的能带结构 回顾 从结合力上看由共价键和离子键混合 化合物半导体从结构上来说具有闪锌矿结构 三五族半导体的能带结构&#xff1a; 1.导带结构 2.价带结构 3.禁带宽度 正课 能带结构的共同特征&#…

docker容器与网络模式|磁盘使用|内存使用|清理

docker容器与网络模式|磁盘使用|内存使用|清理 一 docker 网络模式二 docker的网络模式分类三 网络模式详解3.1host模式&#xff1a;3.2 container模式container模式操作 3.3 none模式3.4 brideg模式3.5 自定义网络 四 容器资源控制4.1 docker容器cpu压力测试4.2 设置CPU资源占…

SpringCloud 微服务随机掉线排查过程

一、背景 我们的业务共使用 11 台&#xff08;阿里云&#xff09;服务器&#xff0c;使用 SpringcloudAlibaba 构建微服务集群, 共计 60 个微服务, 全部注册在同一个 Nacos 集群。 流量转发路径&#xff1a;nginx -> spring-gateway -> 业务微服务。 使用的版本如下&a…

IGA_PLSM3D的理解1

文章目录 前言一、IgaTop3D_FAST.m给的参数二、Material properties 材料特性对Geom_Mod3D的理解 三、IGA准备对Pre_IGA3D的理解 输出1-----CtrPts&#xff1a; 输出2-----Ele&#xff1a; 输出3-----GauPts&#xff1a; 对Boun_Cond3D的理解 输出1-----DBoudary&#xff1a; 输…

Java入坑之类的派生与继承

一、继承 1.1继承的概念 Java中的继承&#xff1a;子类就是享有父类的属性和方法&#xff0c;并且还存在一定的属性和方法的扩展。 Subclass&#xff0c;从另一个类派生出的类&#xff0c;称为子类(派生类&#xff0c;扩展类等) Superclass&#xff0c;派生子类的类&#xff…

PC主流同步软件评测

因为一个要把超过13G的众多零散文件同步到webdav网盘的需求&#xff0c;将市面上一些知名的同步软件试用测试了一番&#xff0c;最终发现只有Syncovery满足需求 先把参与测试的同步软件列一下&#xff1a; GoodSync、FreeFileSync、微软SyncToy、KLS Backup、SecondCopy、Sma…

coreboot seabios

seabios现在是很多虚拟机的默认启动bios&#xff0c;这跟它的短小精干有很大关系&#xff0c;也跟它提供比较完备的legacy支持有关。 按照以下步骤把seabios制作成coreboot的payload&#xff0c; 下载seabios代码&#xff0c; http://code.coreboot.org/p/seabios/downloads…

学生成绩管理系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87700420 更多系统资源库…

【Cpython的GIL详细了解一下?】

简单解释 全局解释器锁&#xff08;Global Interpreter Lock&#xff0c;简称 GIL&#xff09;是 CPython 解释器内部的一个同步原语&#xff0c;它用于在同一时间只允许一个线程执行 Python 字节码。 GIL 的工作原理如下&#xff1a; 在 CPython 中&#xff0c;每个线程都需…

systemctl 命令设置开机自启动失败

1.案例现象 我在 3 月 31日的时候发表了一篇《shell 脚本之一键部署安装 Nginx 》&#xff0c;介绍了如何通过 shell 脚本一键安装 Nginx 我脚本中执行了 Nginx 开机自启动的命令&#xff0c;当我使用 systemctl status nginx 命令复核的时候&#xff0c;我发现 Nginx 服务设…

C# NetCore XML 反序列化解析错误:<xml xmlns=‘‘> was not expected 及 Encoding=utf-16问题

xml帮助类在最后 刚好有业务需要解析xml文件&#xff0c;于是找到帮助类代码开始尝试解析文件&#xff0c;总是出现异常&#xff1a;<xml xmlnsxxxxxxxxxxxxxxxx> was not expected&#xff0c;开始寻找解决方案&#xff1a; 要使结果正确&#xff0c;必须满足两个条件…

【Qt】QString与QChar的源码学习及二者与Unicode的关系【2023.04.20】

简介 关于QString乱码的一些补充。主要就两点&#xff0c;QChar、QString底层存储的字符都是16进制的Unicode编码。 QChar QChar对应16位的Unicode字符集。 The QChar class provides a 16-bit Unicode character. In Qt, Unicode characters are 16-bit entities without an…

Redis RDB 和 AOF原理讲解

redis提供了两种持久化方式。 aof&#xff08;APPEND ON FILE&#xff09;持久化&#xff1a;原理是将redis的操作以命令的方式写入aof文件中&#xff0c;追加。 rdb&#xff08;Redis DataBase&#xff09;内存快照持久化&#xff0c;就是将redis的内存中的数据全量拷贝一份存…

【C++STL精讲】stack与queue的基本使用及模拟实现

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;stack是什么&#xff1f;&#x1f337;stack的基本使用&#x1f337;stack的模拟实现&#x1f337;queue是什么&#xff1f;&#x1f337;queue的基本使用&#x1f337;queue的模拟实现 &#x1f490;专栏导读…

Python基础实战2-Python安装

安装简介 电脑系统&#xff1a;Windwos 10 安装的Python版本&#xff1a;3.7.8 安装Python环境 安装运行Python程序的工具&#xff0c;也称位Python解释器。 初学者可以安装anaconda&#xff0c;里面自带500常用库。 第一步&#xff0c;下载Python 可以在Python官方网站…

mockjs基础及项目使用

Mock介绍 Mock.js 是一款模拟数据生成器&#xff0c;旨在帮助前端攻城师独立于后端进行开发&#xff0c;帮助编写单元测试。提供了以下模拟功能&#xff1a; 1. 根据数据模板生成模拟数据 2. 模拟 Ajax 请求&#xff0c;生成并返回模拟数据 3. 基于 HTML 模板生成模拟数据 以上…

网络安全之ATP

目录 APT 定义 特点 目的 APT攻击的生命周期 阶段一 --- 扫描探测 阶段二 --- 工具投送 阶段三 ---漏洞利用 阶段四 --- 木马植入 阶段五 --- 远程控制 阶段六 --- 横向渗透 阶段七 --- 目标行动 防御APT 最佳有效办法 --- 沙箱技术 沙箱技术 针对APT攻击的防御…

归并排序的递归实现

归并排序是一种比较排序&#xff0c;通过分治法思想来进行实现的&#xff0c;其基本思想是&#xff1a; 将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff0c;即先使每个子序列有序&#xff0c;再使子序列段间有序。 若将两个有序表合并成一个有序表&#xff0c;称…

【轻松开发微信小程序】实现用户增删改查功能

文章目录 前言创建微信小程序项目创建项目目录结构编写首页页面编写编辑页面实现增删改查功能展示最终效果总结写在最后 前言 上一篇博客中我们学习了什么是小程序以及开发一个小程序的具体步骤。 在本篇博客中&#xff0c;我们来开发一个用户列表增删改查功能的完整流程&…