メモリーの安全性
日本語を消す 英語を消す下記URLから引用し、日本語訳をつけてみました
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/memorysafety
Structure your code to avoid conflicts when accessing memory.
メモリにアクセスする際の競合を回避するようにコードを構造化します。
By default, Swift prevents unsafe behavior from happening in your code. For example, Swift ensures that variables are initialized before they’re used, memory isn’t accessed after it’s been deallocated, and array indices are checked for out-of-bounds errors.
デフォルトでは、Swift はコード内での危険な動作の発生を防ぎます。 たとえば、Swift では、変数が使用前に初期化されていること、メモリの割り当てが解除された後はメモリにアクセスされていないこと、配列のインデックスに範囲外のエラーがないかチェックされていることを確認します。
Swift also makes sure that multiple accesses to the same area of memory don’t conflict, by requiring code that modifies a location in memory to have exclusive access to that memory. Because Swift manages memory automatically, most of the time you don’t have to think about accessing memory at all. However, it’s important to understand where potential conflicts can occur, so you can avoid writing code that has conflicting access to memory. If your code does contain conflicts, you’ll get a compile-time or runtime error.
また、Swift は、メモリ内の場所を変更するコードにそのメモリへの排他的アクセスを要求することで、メモリの同じ領域への複数のアクセスが競合しないようにします。 Swift はメモリを自動的に管理するため、ほとんどの場合、メモリへのアクセスについて考える必要はまったくありません。 ただし、メモリへのアクセスが競合するコードの作成を避けるために、潜在的な競合がどこで発生する可能性があるかを理解することが重要です。 コードに競合が含まれている場合は、コンパイル時エラーまたは実行時エラーが発生します。
Understanding Conflicting Access to Memory
メモリへのアクセスの競合についての理解
Access to memory happens in your code when you do things like set the value of a variable or pass an argument to a function. For example, the following code contains both a read access and a write access:
メモリへのアクセスは、変数の値を設定したり、関数に引数を渡したりするときにコード内で発生します。 たとえば、次のコードには読み取りアクセスと書き込みアクセスの両方が含まれています。
// 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)!")
A conflicting access to memory can occur when different parts of your code are trying to access the same location in memory at the same time. Multiple accesses to a location in memory at the same time can produce unpredictable or inconsistent behavior. In Swift, there are ways to modify a value that span several lines of code, making it possible to attempt to access a value in the middle of its own modification.
コードの異なる部分がメモリ内の同じ場所に同時にアクセスしようとすると、メモリへのアクセスの競合が発生する可能性があります。 メモリ内の特定の場所に同時に複数アクセスすると、予測できない動作や一貫性のない動作が発生する可能性があります。 Swift では、コードの数行にまたがる値を変更する方法があり、値自体の変更の途中で値にアクセスすることを試みることができます。
You can see a similar problem by thinking about how you update a budget that’s written on a piece of paper. Updating the budget is a two-step process: First you add the items’ names and prices, and then you change the total amount to reflect the items currently on the list. Before and after the update, you can read any information from the budget and get a correct answer, as shown in the figure below.
紙に書かれた予算を更新する方法を考えてみると、同様の問題がわかります。 予算の更新は 2 段階のプロセスです。最初に商品の名前と価格を追加し、次に現在リストにある商品を反映するように合計金額を変更します。 次の図に示すように、更新の前後で予算から情報を読み取り、正しい答えを得ることができます。
While you’re adding items to the budget, it’s in a temporary, invalid state because the total amount hasn’t been updated to reflect the newly added items. Reading the total amount during the process of adding an item gives you incorrect information.
予算に項目を追加している間は、新しく追加された項目を反映するように合計金額が更新されていないため、予算は一時的に無効な状態になります。 アイテムを追加するプロセス中に合計金額を読み取ると、誤った情報が得られます。
This example also demonstrates a challenge you may encounter when fixing conflicting access to memory: There are sometimes multiple ways to fix the conflict that produce different answers, and it’s not always obvious which answer is correct. In this example, depending on whether you wanted the original total amount or the updated total amount, either $5 or $320 could be the correct answer. Before you can fix the conflicting access, you have to determine what it was intended to do.
この例は、メモリへのアクセスの競合を修正するときに遭遇する可能性のある課題も示しています。競合を修正する方法が複数あるため、異なる答えが得られる場合があり、どの答えが正しいかは必ずしも明らかではありません。 この例では、元の合計金額が必要か、更新された合計金額が必要かに応じて、5 ドルまたは 320 ドルが正しい答えになる可能性があります。 競合するアクセスを修正する前に、そのアクセスの目的を特定する必要があります。
Note
注釈
If you’ve written concurrent or multithreaded code, conflicting access to memory might be a familiar problem. However, the conflicting access discussed here can happen on a single thread and doesn’t involve concurrent or multithreaded code.
同時実行またはマルチスレッドのコードを作成したことがある場合、メモリへのアクセスの競合はよくある問題かもしれません。 ただし、ここで説明するアクセスの競合は単一のスレッドで発生する可能性があり、同時実行コードやマルチスレッド コードは関係しません。
If you have conflicting access to memory from within a single thread, Swift guarantees that you’ll get an error at either compile time or runtime. For multithreaded code, use Thread Sanitizer(Link:developer.apple.com) to help detect conflicting access across threads.
単一スレッド内からメモリへのアクセスが競合している場合、Swift はコンパイル時または実行時にエラーが発生することを保証します。 マルチスレッド コードの場合は、Thread Sanitizer(Link:developer.apple.com)(英語) を使用して、スレッド間で競合するアクセスを検出します。
Characteristics of Memory Access
メモリアクセスの特徴
There are three characteristics of memory access to consider in the context of conflicting access: whether the access is a read or a write, the duration of the access, and the location in memory being accessed. Specifically, a conflict occurs if you have two accesses that meet all of the following conditions:
競合するアクセスのコンテキストでは、メモリ アクセスの 3 つの特性を考慮する必要があります。それは、アクセスが読み取りであるか書き込みであるか、アクセスの期間、およびアクセスされるメモリ内の場所です。 具体的には、次の条件をすべて満たす 2 つのアクセスがある場合に競合が発生します。
- At least one is a write access or a nonatomic access.
- 少なくとも 1 つは書き込みアクセスまたは非原子的 アクセスです。
- They access the same location in memory.
- これらはメモリ内の同じ場所にアクセスします。
- Their durations overlap.
- それらの期間は重複します。
The difference between a read and write access is usually obvious: a write access changes the location in memory, but a read access doesn’t. The location in memory refers to what is being accessed — for example, a variable, constant, or property. The duration of a memory access is either instantaneous or long-term.
読み取りアクセスと書き込みアクセスの違いは通常明らかです。書き込みアクセスではメモリ内の位置が変更されますが、読み取りアクセスでは変更されません。 メモリ内の場所は、変数、定数、プロパティなど、アクセスされる対象を指します。 メモリ アクセスの持続時間は、瞬間的なものである場合もあれば、長期間にわたるものもあります。
An operation is atomic if it uses only C atomic operations; otherwise it’s nonatomic. For a list of those functions, see the stdatomic(3)
man page.
C のアトミック操作のみを使用する操作はアトミックです。 それ以外の場合は非アトミックです。 これらの関数のリストについては、stdatomic(3)
のマニュアル ページを参照してください。
An access is instantaneous if it’s not possible for other code to run after that access starts but before it ends. By their nature, two instantaneous accesses can’t happen at the same time. Most memory access is instantaneous. For example, all the read and write accesses in the code listing below are instantaneous:
アクセスの開始後、終了する前に他のコードを実行できない場合、アクセスは瞬時に行われます。 その性質上、2 つの瞬間的なアクセスは同時に発生することはできません。 ほとんどのメモリ アクセスは瞬時に行われます。 たとえば、以下のコード リストの読み取りおよび書き込みアクセスはすべて瞬時に行われます。
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
However, there are several ways to access memory, called long-term accesses, that span the execution of other code. The difference between instantaneous access and long-term access is that it’s possible for other code to run after a long-term access starts but before it ends, which is called overlap. A long-term access can overlap with other long-term accesses and instantaneous accesses.
ただし、他のコードの実行にまたがる、長期アクセスと呼ばれるメモリにアクセスする方法がいくつかあります。 瞬間的アクセスと長期アクセスの違いは、長期アクセスの開始後、終了する前に他のコードが実行される可能性があることです。これはオーバーラップと呼ばれます。 長期アクセスは、他の長期アクセスや瞬間的なアクセスと重複する可能性があります。
Overlapping accesses appear primarily in code that uses in-out parameters in functions and methods or mutating methods of a structure. The specific kinds of Swift code that use long-term accesses are discussed in the sections below.
重複アクセスは主に、関数やメソッド内で in-out パラメーターを使用するコード、または構造体のメソッドを変更するコードで発生します。 長期アクセスを使用する特定の種類の Swift コードについては、以下のセクションで説明します。
Conflicting Access to In-Out Parameters
In-Outパラメータへのアクセスの競合
A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.
関数には、そのすべてのin-out パラメータに対する長期書き込みアクセス権があります。 in-out パラメータの書き込みアクセスは、すべての非 in-out パラメータが評価された後に開始され、その関数呼び出しの間ずっと持続します。 複数のin-out パラメータがある場合、書き込みアクセスはパラメータが表示されたのと同じ順序で開始されます。
One consequence of this long-term write access is that you can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it — any access to the original creates a conflict. For example:
この長期書き込みアクセスの結果の 1 つは、たとえスコープ規則やアクセス制御によって許可されていたとしても、in-out として渡された元の変数にアクセスできなくなることです。元の変数にアクセスすると競合が発生します。 例えば:
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
In the code above, stepSize
is a global variable, and it’s normally accessible from within increment(_:)
. However, the read access to stepSize
overlaps with the write access to number
. As shown in the figure below, both number
and stepSize
refer to the same location in memory. The read and write accesses refer to the same memory and they overlap, producing a conflict.
上記のコードでは、stepSize
はグローバル変数であり、通常は increment(_:)
内からアクセスできます。 ただし、stepSize
への読み取りアクセスは、number
への書き込みアクセスと重複します。 下の図に示すように、number
と stepSize
は両方ともメモリ内の同じ場所を参照します。 読み取りアクセスと書き込みアクセスは同じメモリを参照し、重複して競合が発生します。
One way to solve this conflict is to make an explicit copy of stepSize
:
この競合を解決する 1 つの方法は、stepSize
の明示的なコピーを作成することです。
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
When you make a copy of stepSize
before calling increment(_:)
, it’s clear that the value of copyOfStepSize
is incremented by the current step size. The read access ends before the write access starts, so there isn’t a conflict.
increment(_:)
を呼び出す前に stepSize
のコピーを作成すると、copyOfStepSize
の値が現在のステップ サイズによって増加することが明らかです。 読み取りアクセスは書き込みアクセスが開始される前に終了するため、競合は発生しません。
Another consequence of long-term write access to in-out parameters is that passing a single variable as the argument for multiple in-out parameters of the same function produces a conflict. For example:
in-out パラメータへの長期書き込みアクセスのもう 1 つの結果は、同じ関数の複数の in-out パラメータの引数として 1 つの変数を渡すと競合が発生することです。 例えば:
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
The balance(_:_:)
function above modifies its two parameters to divide the total value evenly between them. Calling it with playerOneScore
and playerTwoScore
as arguments doesn’t produce a conflict — there are two write accesses that overlap in time, but they access different locations in memory. In contrast, passing playerOneScore
as the value for both parameters produces a conflict because it tries to perform two write accesses to the same location in memory at the same time.
上記の balance(_:_:)
関数は、合計値を均等に分割するために 2 つのパラメーターを変更します。 playerOneScore
と playerTwoScore
を引数として呼び出しても競合は発生しません。時間的に重複する 2 つの書き込みアクセスがありますが、メモリ内の異なる場所にアクセスします。 対照的に、playerOneScore
を両方のパラメータの値として渡すと、メモリ内の同じ場所に対して 2 つの書き込みアクセスを同時に実行しようとするため、競合が発生します。
Note
注釈
Because operators are functions, they can also have long-term accesses to their in-out parameters. For example, if balance(_:_:)
was an operator function named <^>
, writing playerOneScore <^> playerOneScore
would result in the same conflict as balance(&playerOneScore, &playerOneScore)
.
演算子は関数であるため、入出力パラメータに長期間アクセスすることもできます。 たとえば、balance(_:_:)
が <^>
という名前の演算子関数である場合、playerOneScore <^> playerOneScore
と記述すると、balance(&playerOneScore, &playerOneScore)
と同じ競合が発生します。
Conflicting Access to self in Methods
メソッド内での self へのアクセスの競合
A mutating method on a structure has write access to self
for the duration of the method call. For example, consider a game where each player has a health amount, which decreases when taking damage, and an energy amount, which decreases when using special abilities.
構造体の変更メソッドは、メソッド呼び出しの間、self
への書き込みアクセス権を持ちます。 たとえば、各プレイヤーがダメージを受けると減少する健康量と、特殊能力を使用すると減少するエネルギー量を持つゲームを考えてみましょう。
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
In the restoreHealth()
method above, a write access to self
starts at the beginning of the method and lasts until the method returns. In this case, there’s no other code inside restoreHealth()
that could have an overlapping access to the properties of a Player
instance. The shareHealth(with:)
method below takes another Player
instance as an in-out parameter, creating the possibility of overlapping accesses.
上記の restoreHealth()
メソッドでは、自分自身への書き込みアクセスがメソッドの先頭から始まり、メソッドが返されるまで継続します。 この場合、restoreHealth()
内には、Player
インスタンスのプロパティに重複してアクセスできるコードは他にありません。 以下の shareHealth(with:)
メソッドは、別の Player
インスタンスを入出力パラメータとして取得するため、アクセスが重複する可能性があります。
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
In the example above, calling the shareHealth(with:)
method for Oscar’s player to share health with Maria’s player doesn’t cause a conflict. There’s a write access to oscar
during the method call because oscar
is the value of self
in a mutating method, and there’s a write access to maria
for the same duration because maria
was passed as an in-out parameter. As shown in the figure below, they access different locations in memory. Even though the two write accesses overlap in time, they don’t conflict.
上の例では、オスカーのプレーヤーが健康状態をマリアのプレーヤーと共有するために shareHealth(with:)
メソッドを呼び出しても競合は発生しません。 oscar
は変更メソッドの self
の値であるため、メソッド呼び出し中に oscar
への書き込みアクセスが可能です。また、maria
が in-out パラメータとして渡されたため、同じ期間中 maria
への書き込みアクセスが可能です。 以下の図に示すように、これらはメモリ内の異なる場所にアクセスします。 2 つの書き込みアクセスは時間的に重なっていますが、競合しません。
However, if you pass oscar
as the argument to shareHealth(with:)
, there’s a conflict:
ただし、oscar
を引数として shareHealth(with:)
に渡すと、競合が発生します。
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
The mutating method needs write access to self
for the duration of the method, and the in-out parameter needs write access to teammate
for the same duration. Within the method, both self
and teammate
refer to the same location in memory — as shown in the figure below. The two write accesses refer to the same memory and they overlap, producing a conflict.
変更メソッドにはメソッドの継続期間中、self
への書き込みアクセスが必要であり、in-out パラメータには同じ期間、teammate
への書き込みアクセスが必要です。 以下の図に示すように、メソッド内では、self
とteammate
の両方がメモリ内の同じ場所を参照します。 2 つの書き込みアクセスは同じメモリを参照しており、重複して競合が発生します。
Conflicting Access to Properties
プロパティへのアクセスの競合
Types like structures, tuples, and enumerations are made up of individual constituent values, such as the properties of a structure or the elements of a tuple. Because these are value types, mutating any piece of the value mutates the whole value, meaning read or write access to one of the properties requires read or write access to the whole value. For example, overlapping write accesses to the elements of a tuple produces a conflict:
構造体、タプル、列挙などの型は、構造体のプロパティやタプルの要素などの個々の構成値で構成されます。 これらは値型であるため、値の一部を変更すると値全体が変更されます。つまり、いずれかのプロパティへの読み取りまたは書き込みアクセスには、値全体への読み取りまたは書き込みアクセスが必要になります。 たとえば、タプルの要素への書き込みアクセスが重複すると、競合が発生します。
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
In the example above, calling balance(_:_:)
on the elements of a tuple produces a conflict because there are overlapping write accesses to playerInformation
. Both playerInformation.health
and playerInformation.energy
are passed as in-out parameters, which means balance(_:_:)
needs write access to them for the duration of the function call. In both cases, a write access to the tuple element requires a write access to the entire tuple. This means there are two write accesses to playerInformation
with durations that overlap, causing a conflict.
上の例では、playerInformation
への書き込みアクセスが重複しているため、タプルの要素に対してbalance(_:_:)
を呼び出すと競合が発生します。 playerInformation.health
と playerInformation.energy
は両方とも入出力パラメータとして渡されます。つまり、関数呼び出しの間、balance(_:_:)
にはそれらへの書き込みアクセスが必要です。 どちらの場合も、タプル要素への書き込みアクセスには、タプル全体への書き込みアクセスが必要です。 これは、playerInformation
への書き込みアクセスが 2 つあり、重複する期間があり、競合が発生することを意味します。
The code below shows that the same error appears for overlapping write accesses to the properties of a structure that’s stored in a global variable.
以下のコードは、グローバル変数に格納されている構造体のプロパティへの書き込みアクセスが重複した場合に同じエラーが表示されることを示しています。
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
In practice, most access to the properties of a structure can overlap safely. For example, if the variable holly
in the example above is changed to a local variable instead of a global variable, the compiler can prove that overlapping access to stored properties of the structure is safe:
実際には、構造のプロパティへのほとんどのアクセスは安全に重複できます。 たとえば、上記の例の変数 holly
がグローバル変数ではなくローカル変数に変更された場合、コンパイラーは、構造体の保存されたプロパティへの重複アクセスが安全であることを証明できます。
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
In the example above, Oscar’s health and energy are passed as the two in-out parameters to balance(_:_:)
. The compiler can prove that memory safety is preserved because the two stored properties don’t interact in any way.
上の例では、オスカーの健康状態とエネルギーが 2 つの入出力パラメータとしてbalance(_:_:)
へ渡されます。 コンパイラは、2 つの格納されたプロパティがまったく相互作用しないため、メモリの安全性が保たれていることを証明できます。
The restriction against overlapping access to properties of a structure isn’t always necessary to preserve memory safety. Memory safety is the desired guarantee, but exclusive access is a stricter requirement than memory safety — which means some code preserves memory safety, even though it violates exclusive access to memory. Swift allows this memory-safe code if the compiler can prove that the nonexclusive access to memory is still safe. Specifically, it can prove that overlapping access to properties of a structure is safe if the following conditions apply:
構造体のプロパティへの重複アクセスに対する制限は、メモリの安全性を維持するために必ずしも必要というわけではありません。 メモリの安全性は望ましい保証ですが、排他的アクセスはメモリの安全性よりも厳しい要件です。つまり、一部のコードはメモリへの排他的アクセスに違反しているにもかかわらず、メモリの安全性を維持します。 Swift は、コンパイラーがメモリーへの非排他的アクセスが依然として安全であることを証明できれば、このメモリーセーフなコードを許可します。 具体的には、次の条件が当てはまる場合、構造のプロパティへの重複アクセスが安全であることを証明できます。
- You’re accessing only stored properties of an instance, not computed properties or class properties.
- インスタンスの保存されたプロパティのみにアクセスし、計算されたプロパティやクラス プロパティにはアクセスしません。
- The structure is the value of a local variable, not a global variable.
- 構造体はローカル変数の値であり、グローバル変数ではありません。
- The structure is either not captured by any closures, or it’s captured only by nonescaping closures.
- 構造はどのクロージャにもキャプチャされないか、非エスケープ クロージャによってのみキャプチャされます。
If the compiler can’t prove the access is safe, it doesn’t allow the access.
コンパイラがアクセスが安全であることを証明できない場合、アクセスは許可されません。