Swift内存访问冲突

embedded/2024/11/22 22:54:54/

内存的访问,发生在给变量赋值的时候,或者传递值(给函数)的时候,例如

var one = 1//向one的内存区域发起一次写的操作
print("\(one)")//向one的内存区域发起一次读的操作

在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址是指向正在访问的东西(例如一个变量,常量或者属性)的位置的值 。内存访问的时长要么是瞬时的,要么是长期的。如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。正常来说,两个瞬时访问是不可能同时发生的。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的:

func oneMore(than number: Int) -> Int {return number + 1
}var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印“2”

然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。如果发生了重叠访问,就可能会造成内存冲突,因为在同一块内存区域同时发生读和写的操作是肯定不被允许的。

重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。

In-Out 参数的访问冲突

一个函数会对它所有的 in-out 参数保持长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。

这种长期保持的写访问带来的问题是,你不能再访问以 in-out 形式传入的原始变量,即使作用域原则和访问权限允许——任何访问原始变量的行为都会造成冲突。例如:

var stepSize = 1func increment(_ number: inout Int) {number += stepSizeprint(number)//不会产生读访问
}increment(&stepSize)
// 错误:stepSize 访问冲突

代码中stepSize是一个全局变量,并且我们以inout的形式将该变量传入了increment函数当中,这里我们得知道的是,一个函数会对它所有的 in-out 参数保持长期写访问,inout参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。这种方法的带来的弊端就是,你不能在访问以inout形式传入的原始变量(我都得到地址了,我还访问原始干嘛,对吧),即使作用域原则和访问权限允许——任何访问原始变量的行为都会造成冲突。上述代码中,由于inout带来的长期写访问,而我们也对stepSize进行了读访问。所以就会造成内存冲突。(当然,同一函数传入两个inout相同的数据,也会造成写访问冲突)但是为什么print(number)不回发生内存冲突呢?实际上,将inout传入进去时,swift发生了以下操作

	1.	写访问发生在调用期间:•	变量 stepSize 被作为 inout 参数传递给 increment。•	Swift 会暂时暂停对原始变量的所有访问,并将其访问权转移给函数的 inout 参数 number。2.	对原变量的隔离:•	在函数调用期间,stepSize 被隔离,外部无法访问。•	任何对 number 的读写操作都会作用于 stepSize 的值,但此时只通过 number 访问变量。3.	生命周期的非交叠:•	函数调用开始时,stepSize 的值被写入到 number 的内存。•	在函数调用结束时,number 的值被写回到 stepSize。•	在调用期间,stepSize 不再直接可用,因此不存在交叠访问。

解决冲突的方式很多,这里举例一种很鸡肋的方式:

// 显式拷贝
var copyOfStepSize = stepSize
increment(&copyOfStepSize)// 更新原来的值
stepSize = copyOfStepSize
// stepSize 现在的值是 2

方法里self带来访问冲突

废话少说,上图

struct Player {var name: Stringvar health: Intvar energy: Intstatic let maxHealth = 10mutating func restoreHealth() {health = Player.maxHealth}
}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)  // 正常
oscar.shareHealth(with: &Oscar)  // 错误:oscar 访问冲突

在上述代码中我们在plyer结构题方法中对restoreHealth使用mutating访问权限,这样就会一直有对self的写访问,而在oscar.shareHealth我们传入了oscar,shareHealth中的inout又对Player进行的写访问,就造成了两个写访问冲突。所以会造成内存

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

崩溃。有人疑惑为什么上面的restoreHealth()中不会发生内存冲突呢?

我们可以看到,maxHealth我们是是用static静态修饰的,他属于类型本身,而不属于任何实例,他储单独存储在静态区域。所以访问自然与实例无关了。

属性的访问冲突

如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问一整个值。例如,元组元素的写访问重叠会产生冲突:

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 错误:playerInformation 的属性访问冲突

在结构体中,下面展示了对于一个存储在全局变量里的结构体属性的写访问重叠了。

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

但是对于本地变量,编译器就可以,编译器就可以保证重叠访问是安全的(不懂但尊重)。

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

以下就是一些规则,它可以保证结构体属性的重叠访问是安全的:

·你访问的是实例的存储属性,而不是计算属性或类的属性

·结构体是本地变量的值,而非全局变量

·结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了


http://www.ppmy.cn/embedded/139728.html

相关文章

pycharm复现github项目代码问题记录

目录 1.anaconda下载问题2.创建项目虚拟环境---在Anaconda Prompt中或在pycharm终端里3.conda安装与卸载4.镜像源附录 1.anaconda下载问题 按照教程下载后,配置的环境全部保存在了C盘,导致C盘爆满: 按照如下进行修改,将环境地址放…

多目标优化算法:多目标蛇鹫优化算法(MOSBOA)求解ZDT1、ZDT2、ZDT3、ZDT4、ZDT6,提供完整MATLAB代码

一、蛇鹫优化算法 蛇鹫优化算法(Secretary Bird Optimization Algorithm,简称SBOA)由Youfa Fu等人于2024年4月发表在《Artificial Intelligence Review》期刊上的一种新型的元启发式算法。该算法旨在解决复杂工程优化问题,特别是…

跨平台编译Go程序:GOOS和GOARCH环境变量的使用

在Go语言开发中,我们经常需要为不同的操作系统和处理器架构编译程序。Go语言提供了两个环境变量GOOS和GOARCH,允许我们轻松地为不同的目标平台编译代码。本文将介绍如何使用这两个环境变量来编译适用于不同平台和架构的Go程序。 1. 理解GOOS和GOARCH G…

Spring Boot汽车资讯:速度与信息的融合

3系统分析 3.1可行性分析 通过对本汽车资讯网站实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本汽车资讯网站采用SSM框架,JAVA作为开发语言&#…

基于Java实现的(GUI)飞机大战小游戏

摘要 本课程设计通过代码实现将理论知识和具体实践相结合,巩固提高了对JAVA的相关方法与概念的理解,进一步加强了学生的发散思维及动手能力,加强了学生对计算机及软件工程的进一步了解。 在这个课程设计中,使用类、抽象类和接口…

Spark RDD 中的 repartition 和 coalesce 是两种常用的分区调整算子的异同点

Spark RDD 中的 repartition 和 coalesce 是两种常用的分区调整算子,它们的功能是改变 RDD 的分区数量。以下从源码、原理和使用角度分析它们的异同点。 一、repartition 和 coalesce 的功能与区别 特性repartitioncoalesce主要功能调整 RDD 分区数量,可…

如何配置 Flink CDC 连接 OceanBase 实现数据实时同步

在大数据处理方面,Flink CDC(Change Data Capture)是一款功能强大的工具,它能实时获取数据库中的变更数据,并将这些数据传送给其他系统进行后续处理。 Flink CDC 结合 OceanBase 分布式数据库高性能、HTAP等特性&#…

nvm和nrm的安装与使用

NVM相关请跳转: Node版本管理器nvm的安装与使用 nrm 的安装与使用 nrm(NPM Registry Manager)是一个用于管理和切换 NPM 源的工具。它允许你在多个 NPM 源之间快速切换,以提高包管理的速度和效率。以下是 nrm 的安装和使用方法&…