1
- <!--
2
- 要翻译的文件:https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/blob/swift-6-beta-translation/swift-6-beta.docc/LanguageGuide/MemorySafety.md
3
- Swift 文档源文件地址:https://docs.swift.org/swift-book/documentation/the-swift-programming-language/memorysafety
4
- 翻译估计用时:⭐️⭐️⭐️⭐️⭐️
5
- -->
1
+ # 内存安全
6
2
7
- # Memory Safety
3
+ 通过合理地组织代码来避免内存访问冲突。
8
4
9
- Structure your code to avoid conflicts when accessing memory.
5
+ 默认情况下,Swift 会阻止代码中不安全的行为。例如,Swift 会确保所有变量都在使用前被初始化、内存不可在被释放后访问,还会对数组的下标做越界检查。
10
6
11
- By default, Swift prevents unsafe behavior from happening in your code.
12
- For example,
13
- Swift ensures that variables are initialized before they're used,
14
- memory isn't accessed after it's been deallocated,
15
- and array indices are checked for out-of-bounds errors.
16
-
17
- Swift also makes sure that multiple accesses
18
- to the same area of memory don't conflict,
19
- by requiring code that modifies a location in memory
20
- to have exclusive access to that memory.
21
- Because Swift manages memory automatically,
22
- most of the time you don't have to think about accessing memory at all.
23
- However,
24
- it's important to understand where potential conflicts can occur,
25
- so you can avoid writing code that has conflicting access to memory.
26
- If your code does contain conflicts,
27
- you'll get a compile-time or runtime error.
7
+ Swift 还会要求修改内存的代码拥有对被修改区域的独占访问权,以确保对同一块内存区域的多次访问不会产生冲突。由于 Swift 会自动管理内存,大多数情况下你不需要考虑内存访问相关的问题。但是,为了避免编写出会产生内存访问冲突的代码,你还是很有必要了解什么情况下会出现这种问题。如果你的代码有内存访问冲突的问题,系统会在编译时或运行时报错。
28
8
29
9
<!--
30
10
TODO: maybe re-introduce this text...
@@ -34,19 +14,15 @@ you'll get a compile-time or runtime error.
34
14
Unsafe access to memory is available, if you ask for it explicitly...
35
15
-->
36
16
37
- ## Understanding Conflicting Access to Memory
17
+ ## 理解内存访问冲突
38
18
39
- Access to memory happens in your code
40
- when you do things like set the value of a variable
41
- or pass an argument to a function.
42
- For example,
43
- the following code contains both a read access and a write access:
19
+ 在做为变量赋值、给函数传参这样的事情时,你的代码会访问内存。举个例子,下面的代码包含了一次读操作和一次写操作:
44
20
45
21
``` swift
46
- // A write access to the memory where one is stored.
22
+ // 向 one 所在的内存区域发起一次写操作
47
23
var one = 1
48
24
49
- // A read access from the memory where one is stored.
25
+ // 向 one 所在的内存区域发起一次读操作
50
26
print (" We're number \( one ) !" )
51
27
```
52
28
@@ -68,113 +44,43 @@ print("We're number \(one)!")
68
44
or else I'm going to keep getting "We are Number One" stuck in my head.
69
45
-->
70
46
71
- A conflicting access to memory can occur
72
- when different parts of your code are trying
73
- to access the same location in memory at the same time.
74
- Multiple accesses to a location in memory at the same time
75
- can produce unpredictable or inconsistent behavior.
76
- In Swift, there are ways to modify a value
77
- that span several lines of code,
78
- making it possible to attempt to access a value
79
- in the middle of its own modification.
80
-
81
- You can see a similar problem
82
- by thinking about how you update a budget
83
- that's written on a piece of paper.
84
- Updating the budget is a two-step process:
85
- First you add the items' names and prices,
86
- and then you change the total amount
87
- to reflect the items currently on the list.
88
- Before and after the update,
89
- you can read any information from the budget
90
- and get a correct answer,
91
- as shown in the figure below.
47
+ 当代码中的不同部分试图访问同一块内存区域时,访问冲突就有可能出现。对一块内存区域的同时访问可能导致程序出现无法预测或不稳定的行为。Swift 中有许多修改数值的方式,其中一些会横跨多行代码,这意味着修改某个数值的过程本身也有可能产生对此数值的访问。
48
+
49
+ 要理解这个问题,你可以尝试想象一下在纸上更新一个预算表的流程。更新预算表分为两步,第一步你需要先添加每个预算项目的名字和数额,第二步才是更新预算总额。在整个更新流程的之前及之后,你可以从预算中读取任何信息,而这些信息都是正确的,就像下图所示一样。
92
50
93
51
![ ] ( memory_shopping )
94
52
95
- While you're adding items to the budget,
96
- it's in a temporary, invalid state
97
- because the total amount hasn't been updated
98
- to reflect the newly added items.
99
- Reading the total amount
100
- during the process of adding an item
101
- gives you incorrect information.
102
-
103
- This example also demonstrates
104
- a challenge you may encounter
105
- when fixing conflicting access to memory:
106
- There are sometimes multiple ways to fix the conflict
107
- that produce different answers,
108
- and it's not always obvious which answer is correct.
109
- In this example,
110
- depending on whether you wanted the original total amount
111
- or the updated total amount,
112
- either $5 or $320 could be the correct answer.
113
- Before you can fix the conflicting access,
114
- you have to determine what it was intended to do.
115
-
116
- > Note: If you've written concurrent or multithreaded code,
117
- > conflicting access to memory might be a familiar problem.
118
- > However,
119
- > the conflicting access discussed here can happen
120
- > on a single thread and
121
- > * doesn't* involve concurrent or multithreaded code.
53
+ 但是,在你向预算表添加项目的过程中,它会短暂地处于一个临时、不合法的状态,因为预算总额此时还没有被更新以反映这些新添加的项目。在项目添加的过程中读取到的总额是不正确的。
54
+
55
+ 这个例子还展示了一个在你修复内存访问冲突问题中会遇到的挑战:有时可能存在多种修复冲突的方式,但它们最终产生的结果并不相同,且很难确定到底哪种结果是符合预期的。在这个例子中,$5 或者 $320 都可以是正确答案 —— 这取决于你的「预期」是读取到更新前的总额还是更新后的总额。在你修复访问冲突之前,你需要先明确内存访问的目的和预期结果是什么。
56
+
57
+ > 注意:如果你编写过并发或多线程代码,访问冲突问题可能对你来说并不陌生。但是,这里讨论的访问冲突即便在单线程环境中也有可能产生,而此时并没有并发或多线程的代码存在。
122
58
>
123
- > If you have conflicting access to memory
124
- > from within a single thread,
125
- > Swift guarantees that you'll get an error
126
- > at either compile time or runtime.
127
- > For multithreaded code,
128
- > use [ Thread Sanitizer] ( https://developer.apple.com/documentation/xcode/diagnosing_memory_thread_and_crash_issues_early )
129
- > to help detect conflicting access across threads.
59
+ > 如果你的程序在单线程上运行时产生了内存访问冲突问题,Swift 保证会抛出编译时或运行时错误。对于多线程代码,你可以使用 [ Thread Sanitizer] ( https://developer.apple.com/documentation/xcode/diagnosing_memory_thread_and_crash_issues_early ) 来检测不同线程间的访问冲突。
130
60
131
61
<!--
132
62
TODO: The xref above doesn't seem to give enough information.
133
63
What should I be looking for when I get to the linked page?
134
64
-->
135
65
136
- ### Characteristics of Memory Access
137
-
138
- There are three characteristics of memory access
139
- to consider in the context of conflicting access:
140
- whether the access is a read or a write,
141
- the duration of the access,
142
- and the location in memory being accessed.
143
- Specifically,
144
- a conflict occurs if you have two accesses
145
- that meet all of the following conditions:
146
-
147
- - At least one is a write access or a nonatomic access.
148
- - They access the same location in memory.
149
- - Their durations overlap.
150
-
151
- The difference between a read and write access
152
- is usually obvious:
153
- a write access changes the location in memory,
154
- but a read access doesn't.
155
- The location in memory
156
- refers to what is being accessed ---
157
- for example, a variable, constant, or property.
158
- The duration of a memory access
159
- is either instantaneous or long-term.
160
-
161
- An operation is * atomic*
162
- if it uses only C atomic operations;
163
- otherwise it's nonatomic.
164
- For a list of those functions, see the ` stdatomic(3) ` man page.
66
+ ### 内存访问的特点
67
+
68
+ 在访问冲突的语境下,我们需要考虑内存访问的三个特点:此次访问是读还是写、访问的时长、被访问内存区域的位置。特别地,两次内存访问会在满足以下所有条件时产生冲突:
69
+
70
+ - 至少其中一次访问是写入操作或非原子化访问。
71
+ - 它们访问的是同一块内存区域。
72
+ - 它们的时间窗口出现了重叠。
73
+
74
+ 读操作和写操作之间的区别是显而易见的:写操作会改变内存区域,而读操作不会。「内存区域」指的是被访问的内容(例如变量、常量、属性)。内存访问的时长要么瞬间完成,要么持续较长时间。
75
+
76
+ 如果一项操作仅仅使用了 C 的原子化操作,则这项操作本身也是** 原子化** 的。请查看 ` stdatomic(3) ` 手册来了解有哪些函数满足这个定义。
165
77
166
78
<!--
167
79
Using these functions from Swift requires some shimming -- for example:
168
80
https://github.com/apple/swift-se-0282-experimental/tree/master/Sources/_AtomicsShims
169
81
-->
170
82
171
- An access is * instantaneous*
172
- if it's not possible for other code to run
173
- after that access starts but before it ends.
174
- By their nature, two instantaneous accesses can't happen at the same time.
175
- Most memory access is instantaneous.
176
- For example,
177
- all the read and write accesses in the code listing below are instantaneous:
83
+ 如果在一次内存访问的过程中没有任何其他代码可以在其开始后、结束前运行,则这次访问是** 瞬时** 完成的。其性质决定了两次瞬时访问不可能同时发生。大多数内存访问都是瞬时完成的。比如,下面这段代码中的所有读写操作都是瞬时完成的:
178
84
179
85
``` swift
180
86
func oneMore (than number : Int ) -> Int {
@@ -202,39 +108,17 @@ print(myNumber)
202
108
```
203
109
-->
204
110
205
- However,
206
- there are several ways to access memory,
207
- called * long-term* accesses,
208
- that span the execution of other code.
209
- The difference between instantaneous access and long-term access
210
- is that it’s possible for other code to run
211
- after a long-term access starts but before it ends,
212
- which is called * overlap* .
213
- A long-term access can overlap
214
- with other long-term accesses and instantaneous accesses.
215
-
216
- Overlapping accesses appear primarily in code that uses
217
- in-out parameters in functions and methods
218
- or mutating methods of a structure.
219
- The specific kinds of Swift code that use long-term accesses
220
- are discussed in the sections below.
221
-
222
- ## Conflicting Access to In-Out Parameters
223
-
224
- A function has long-term write access
225
- to all of its in-out parameters.
226
- The write access for an in-out parameter starts
227
- after all of the non-in-out parameters have been evaluated
228
- and lasts for the entire duration of that function call.
229
- If there are multiple in-out parameters,
230
- the write accesses start in the same order as the parameters appear.
231
-
232
- One consequence of this long-term write access
233
- is that you can't access the original
234
- variable that was passed as in-out,
235
- even if scoping rules and access control would otherwise permit it ---
236
- any access to the original creates a conflict.
237
- For example:
111
+ 然而,也有其他被称作** 长时访问** 的内存访问 —— 它们的执行过程会「横跨」其它代码的执行。长时访问和瞬时访问的区别在于:前者执行开始后、结束前的这段时间内,其它的代码有可能会执行,我们称之为** 重叠** 。一次长时访问可以与其它的长时或瞬时访问重叠。
112
+
113
+ 重叠访问通常出现在函数和方法的 in-out 参数以及结构体的变值方法中。下文中会讨论 Swift 中具体哪些类型的代码会使用长时访问。
114
+
115
+
116
+
117
+ ## 对 In-Out 参数的访问冲突
118
+
119
+ 一个函数会对它所有的 in-out 参数保持长时写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果存在多个 in-out 参数,则写访问的开始顺序和参数的排列顺序一致。
120
+
121
+ 这种长期保持的写访问带来的问题是:即便作用域和访问权限规则允许,你也不能再访问以 in-out 形式传入的原始变量。这是因为任何访问原始变量的行为都会造成冲突,例如:
238
122
239
123
``` swift
240
124
var stepSize = 1
@@ -244,7 +128,7 @@ func increment(_ number: inout Int) {
244
128
}
245
129
246
130
increment (& stepSize)
247
- // Error: conflicting accesses to stepSize
131
+ // 错误: stepSize 访问冲突
248
132
```
249
133
250
134
<!--
@@ -265,31 +149,20 @@ increment(&stepSize)
265
149
```
266
150
-->
267
151
268
- In the code above,
269
- ` stepSize ` is a global variable,
270
- and it's normally accessible from within ` increment(_:) ` .
271
- However,
272
- the read access to ` stepSize ` overlaps with
273
- the write access to ` number ` .
274
- As shown in the figure below,
275
- both ` number ` and ` stepSize ` refer to the same location in memory.
276
- The read and write accesses
277
- refer to the same memory and they overlap,
278
- producing a conflict.
152
+ 在上面的代码里,` stepSize ` 是一个全局变量,并且它可以通常可以在 ` increment(_:) ` 里被访问。然而,对于 ` stepSize ` 的读访问与 ` number ` 的写访问重叠了。就像下面展示的那样,` number ` 和 ` stepSize ` 都指向了同一个内存区域。同一块内存区域的读和写访问重叠了,因此产生了冲突。
279
153
280
154
![ ] ( memory_increment )
281
155
282
- One way to solve this conflict
283
- is to make an explicit copy of ` stepSize ` :
156
+ 其中一个解决冲突的方式是显式地复制一份 ` stepSize ` :
284
157
285
158
``` swift
286
- // Make an explicit copy.
159
+ // 显式复制
287
160
var copyOfStepSize = stepSize
288
161
increment (& copyOfStepSize)
289
162
290
- // Update the original.
163
+ // 更新原来的值
291
164
stepSize = copyOfStepSize
292
- // stepSize is now 2
165
+ // stepSize 现在的值是 2
293
166
```
294
167
295
168
<!--
@@ -311,6 +184,8 @@ stepSize = copyOfStepSize
311
184
```
312
185
-->
313
186
187
+ 当你在调用 ` increment(_:) ` 前复制了一份 ` stepSize ` ,
188
+
314
189
When you make a copy of ` stepSize ` before calling ` increment(_:) ` ,
315
190
it's clear that the value of ` copyOfStepSize ` is incremented
316
191
by the current step size.
@@ -719,14 +594,14 @@ it doesn't allow the access.
719
594
::
720
595
721
596
var global = 4
722
-
597
+
723
598
func foo(_ x: UnsafePointer<Int>){
724
599
global = 7
725
600
}
726
-
601
+
727
602
foo(&global)
728
603
print(global)
729
-
604
+
730
605
// Simultaneous accesses to 0x106761618, but modification requires exclusive access.
731
606
// Previous access (a read) started at temp2`main + 87 (0x10675e417).
732
607
// Current access (a modification) started at:
0 commit comments