By default, Swift blocks unsafe behavior in code. For example, Swift ensures that it is initialized before use, that the memory can no longer be accessed after a variable is freed, and that arrays check for out-of-bounds errors.
Swift also ensures that code has exclusive access to the same memory by requiring the marking of memory locations to ensure that multiple accesses to the same memory do not conflict. Since Swift automatically manages memory, you don’t need to worry about accessing memory at all in most cases. If your code has memory access conflicts, you will get compile-time or run-time errors.
Understand memory access conflicts
Memory access happens when you do something like set the value of a variable or pass an actual parameter to a function. For example, the following code contains both read and write access:
* / /Write accessvar one = 1
* / /Read accessprint("We're number \ (one)!").Copy the code
Memory access conflicts can occur when trying to access the same block of memory from different places at the same time. Accessing the same memory in multiple places at the same time can result in unpredictable or inconsistent behavior. In Swift, there are several ways to modify a value that spans multiple lines of code so that it can make its own modifications in the middle of an access value.
You can imagine a similar problem like how do you update a budget on a piece of paper. Updating the budget is a two-step process: first you add the name and price of the item, then you change the total price to show the changes in the current list. Before and after the update, you can read any budget information and get the correct result, as shown in the following legend:
When you add the item to the budget, it is in a temporary state, which is not available because the total price has not been updated to show the latest price. Reading the total price while adding a new item will give you the wrong information. This example also shows the challenges you can encounter when fixing memory access conflicts: sometimes multiple ways of fixing conflicts will produce different results, and it’s often not obvious which one is the right one. In this example, depending on whether you want the original total or the updated total, either $5 or $320 May be correct. Before you can fix memory access conflicts, you must decide which one you want.
Memory access conflicts can be a common problem if you are writing concurrent or multithreaded code. In summary, the access conflicts we discuss here can also occur in a single thread and do not involve concurrent and multithreaded code. If you encounter a memory access conflict in a single thread, Swift ensures that you get an error either at compile time or run time. For multithreaded code, Thread Sanitizer is used to help detect access conflicts between threads.
Typical memory access
There are three typical types of memory access to be considered in the context of access collisions: whether the access is read or written, during the access, and when the memory address is accessed. Specifically, a conflict occurs when you have two visits that satisfy the following conditions:
- At least one of them is
write
To visit orThe atomic
Access; - What they access is
The same block
Memory; - Their access
time
Overlap.
The difference between read and write is usually obvious: write access changes memory, but read access does not. Memory addresses refer to things that are accessed — for example, variables, constants, or attributes. Access to memory is either immediate or prolonged. An operation is atomic if it uses only C atom operations; Otherwise it’s nonatomic. For a list of these functions, see the MAN page for STDatomic. If an access is initiated and no other code can execute it until it finishes, the access is immediate. Due to their nature, two instant accesses cannot occur at the same time. Most memory access is instantaneous. For example, all the read and write accesses listed below are immediate:
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
* / / Prints “2"*
Copy the code
In short, there are many ways to access memory, such as what is called long access, across other code execution processes. The difference between long access and instant access is that long access starts and the rest of the code can still run before it ends, which is called overlap. Long – duration access can overlap with other long – duration access and instant access. Overlapped access mainly occurs in functions and methods or structures that use input and output formal parameters. Specific types of Swift codes that use long access are detailed below.
Access conflicts for input/output formal parameters
A function that has long write access to all of its own input and output form arguments. Write access to input/output formal arguments begins after all non-input/output formal arguments are evaluated and continues until the end of the entire function call. If there are multiple input/output formal parameters, write access begins in the order in which the formal parameters appear. One consequence of this long write access is that you cannot access the original variable passed as input and output, even though scope and access control may allow you to do so — any access to the original variable will cause conflicts, such as: In the code below, stepSize is a global variable that is normally accessible in increment(_:). In short, the read access of stepSize overlaps the write access of number. As shown in the figure below, number and stepSize refer to the same memory address. Read and write accesses refer to the same memory and overlap, creating conflicts.
Defining a global variable, passing it into a method with the inout keyword, and using it in the method, creates an access conflict by both reading and writing to the variable.
var stepSize = 1
func increment(_ number: inout Int) {
number + = stepSize
}
increment(&stepSize)
* / / Error: conflicting accesses to stepSize*
Copy the code
One way to resolve this conflict is to explicitly make a copy of the stepSize (this is easy to understand, the general idea of read-write separation) :
* / / Make an explicit copy. *
var copyOfStepSize = stepSize
increment(©OfStepSize)
* / / Update the original. *
stepSize = copyOfStepSize
* / / stepSize is now 2*
* / / stepSize is now 2*
Copy the code
When you add a copy of stepSize before calling increment(_:), obviously copyOfStepSizeis increases based on the current stepSize. The read access ends before the write access begins, so there are no more conflicts.
Another consequence of long write access to input/output formal arguments is that passing a single variable as an argument conflicts with multiple input/output formal arguments of the same function. For example, multiple inout parameters are defined, but the same argument is passed in
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) * / / OK*
balance(&playerOneScore, &playerOneScore)
* / / Error: Conflicting accesses to playerOneScore*
Copy the code
~~ above balance(::) modifies its two formal parameters to evenly distribute their totals. Using playerOneScore and playerTwoScore as the actual parameters does not conflict — there are two write accesses that overlap at the same time, but they access different memory addresses. In contrast, passing playerOneScore as the value of two formal parameters creates a conflict because it tries to perform two writes to the same memory address and at the same time. ~~ read for a long time also did not understand this paragraph want to say what
An access conflict to self in a method
For example, passing self as an inout argument to your own method causes access collisions, while passing another instance causes no access collisions. Just look at the image if you know what I mean
Mutant methods in a structure can write access to self at method call time. For example, imagine a game where every player has health, which is reduced when the player is injured, and an energy that is reduced when using skills.
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth(a) {
health = Player.maxHealth
}
}
Copy the code
In the restoreHealth() method above, write access to self starts at the beginning of the method and ends at the method return. In this case, there is no other code in restoreHealth() that might overlap to access properties in the Player instance. The following shareHealth(with:) method takes another Player instance as an input/output parameter, creating the possibility of overlapping access:
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var Oscar = Player(the name:"Oscar"The health,10, energy: 10)
var Maria = Player(the name:"Maria"The health,5, energy: 10)
oscar.shareHealth(with: &Maria) * / / OK*
Copy the code
In the above example, calling Oscar player’s shareHealth(with:) method to shareHealth bars does not cause conflict for Maria players. There’s only one write that accesses Oscar during the method call because Oscar is the self value in the mutated method, and there’s only one write that accesses Maria at any one time because Maria is passed as an input/output parameter. As the legend below shows, they access different addresses in memory. Even if their write accesses are simultaneous, they are not in conflict.
Anyway, if you takeoscar
As an actual parameter toshareHealth(with:)
Conflict arises:
oscar.shareHealth(with: &Oscar)
* / / Error: conflicting accesses to Oscar*
Copy the code
The mutant method needs to write access to self during the method itself, and the input/output parameter also needs to write access to the teammate. In the method, self and teammate actually reference the same memory address — as shown. The two write accesses refer to the same address and overlap, causing a conflict.
Property access conflict
If different parameters of the same global tuple/structure are entered, there will be access conflicts. However, adding different parameters to the tuple/structure of a local variable does not cause access conflicts for that tuple/structure. (The example does not verify that tuples of local variables cause access conflicts, if you are interested in doing so)
Types such as structs, tuples, and enumerations are made up of individual values, such as properties of a structure or elements of a tuple. Since these are value types, changing any one value changes the entire type, meaning that read or write access to these attributes requires read/write access to the entire value. For example, overlapping write access to elements of a tuple can cause conflicts:
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
* / / Error: conflicting access to properties of playerInformation*
Copy the code
In the above example, calling balance(_:_:) on elements of the same tuple causes a conflict because of overlapping write access to playerInformation. Playerinformation. health and playerInformation.energy are passed as input/output parameters, which means that balance(_:_:) has write access to them during the function call. In both cases, write access to a tuple element requires write access to the entire tuple. This means that playerInformation has two write access overlaps during the call, resulting in a conflict. The following code shows overlapping write access to properties of the global variable structure, resulting in the same error.
var holly = Player(the name:"Holly"The health,10, energy: 10)
balance(&holly.health, &holly.energy) * / / Error*
Copy the code
In fact, most access to structure properties can safely overlap. If Holly becomes local instead of global, then the compiler can guarantee that the superposition access structure’s stored properties are safe:
func someFunction(a) {
var Oscar = Player(the name:"Oscar"The health,10, energy: 10)
balance(&oscar.health, &oscar.energy) * / / OK*
}
Copy the code
In the example above, Oscar’s health bar and energy are passed to balance(_:_:) as two input/output parameters. The compiler can prove that memory safety is guaranteed because the two storage properties do not interact in any way. Restricting attributes of overlapping access constructs is not always necessary to ensure memory safety. Memory security is a required guarantee, but exclusive access is a more stringent requirement than memory security — that is, some code guarantees memory security even though it violates exclusive access.
Swift allows memory-safe code if the compiler can guarantee that non-exclusive access to memory remains safe. Specifically, the attributes of an overlapping access structure are safe if the following conditions can be met:
- You only access the instance
Storage properties
, notCalculate attribute
orClass attribute
; - Structure is
A local variable
Rather thanThe global variable
; - The structure is either
Not caught by a closure
Only by eitherNon-escape closure capture
.
If the compiler cannot guarantee that access is secure, it will not allow access.
Swift learning group
Welcome to join my Swift learning wechat group for mutual supervision. My wechat account is Reese90