Swift Memory Safety
Structure your code to avoid conflicts when accessing memory.
1. Memory Safety π©βπ»
κΈ°λ³Έμ μΌλ‘ Swift λ μ½λμμ μμ νμ§ μμ μλμ΄ λ°μνλ κ²μ λ°©μ§νλ€. μλ₯Ό λ€λ©΄, Initialization μ΄μ μ Variables μ μ κ·ΌνκΈ°, Deallocated μ΄ν λ©λͺ¨λ¦¬μ μ κ·ΌνκΈ°, Array μ λ²μ 체ν¬(out-of-bounds)μ κ°μ κ²λ€μ΄λ€.
λν Swift λ λμΌν λ©λͺ¨λ¦¬ 곡κ°μ λν Multiple Accesses λ°μμ, ν΄λΉ λ©λͺ¨λ¦¬λ₯Ό μμ μ€μΈ μ½λμκ²
Exclusive Access(λ
μ μ μΈ μ κ·Ό)
μ νλλ‘ ν΄ Conflicts
μ΄ λ°μλμ§ μλλ‘ νλ€.
Swift λ λ©λͺ¨λ¦¬λ₯Ό μλμΌλ‘ κ΄λ¦¬νκΈ° λλ¬Έμ λλΆλΆμ κ²½μ°μ λ©λͺ¨λ¦¬ μ κ·Όμ λν΄ μκ°ν νμκ° μλ€. κ·Έλ¬λ, Conflicts
μ΄ λ°μν κ°λ₯μ±μ΄
μλ κ²½μ°μ λν΄ μμμΌ λ©λͺ¨λ¦¬ μ κ·Όμ λν Conflicting Access λ₯Ό νΌν μ μμΌλ―λ‘ μ΄κ²μ μ΄ν΄νλ κ²μ μ€μνλ€. λ§μ½ μ΄λ₯Ό νΌνμ§ λͺ»ν΄
Conflicts μ μΌμΌν¬ μ μλ μ½λκ° ν¬ν¨λμ΄ μλ€λ©΄, Compile-time Error
λλ Runtime Error
κ° λ°μνλ€.
2. Memory Access π©βπ»
1. Understanding Conflicting Access to Memory
λ©λͺ¨λ¦¬μ μ κ·Όνλ κ²μ λ³μμ κ°μ μ€μ νκ±°λ ν¨μμ arguments λ₯Ό μ λ¬νλ κ²κ³Ό κ°μ μλμ ν λ λ°μνλ€. λ€μ μ½λλ λ©λͺ¨λ¦¬ μ κ·Όμ
Read Access
μ 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)!")
μ½λμ μλ‘ λ€λ₯Έ λΆλΆμ΄ λ©λͺ¨λ¦¬μ λμΌ μμΉμ λμμ μ κ·Όνλ €λ κ²½μ° μμΈ‘ν μ μκ±°λ μΌκ΄μ± μλ μλμ΄ λ°μν μ μκ³ , μ΄λ‘ μΈν΄
Conflicting Access
κ° λ°μν μ μλ€. Swift μλ μ½λμ μ¬λ¬ λΌμΈμ κ±Έμ³ μλ κ°μ μμ νλ λ°©λ²μ΄ μμ΄, μ체 μμ μ€μ κ°μ μ κ·Όμ
μλν μ μλ€. λ€μ μ½λλ μ΄λ° μν©μ λν μμλ₯Ό 보μ¬μ€λ€.
μμ° μ λ°μ΄νΈλ 2λ¨κ³λ‘ μ΄λ£¨μ΄μ§λ€.
- 1λ¨κ³ : μμ΄ν μ λ΄λλ€.
- 2λ¨κ³ : Total μ μ λ°μ΄νΈ νλ€.
2λ¨κ³κΉμ§ μ’ λ£λμ΄ μμ° μ λ°μ΄νΈκ° μλ£λ νμλ μ¬λ°λ₯Έ κ°μ μ»μ μ μλ€. νμ§λ§ 1λ¨κ³λ§ μλ£λ μμ μ Total μ μ κ·Όν κ²½μ°, μμμ μΌλ‘ μ¬λ°λ₯΄μ§ μμ κ°μ μ»λλ€.
νμ§λ§ μ¬λ°λ₯΄μ§ μμ κ°μ μ»λλ€λ κ²μ κ·Έλ¦Όμ βDuringβ μ‘°κ° νλλ§ λ³΄μμ λ μ΄μΌκΈ°μΌ λΏμ΄λ€. νλ‘κ·Έλλ° κ΄μ μμ 보면 μ΄μ κ°μ λ¬Έμ λ₯Ό
ν΄κ²°νλ λ°©λ²μ μ¬λ¬ κ°μ§κ° μ‘΄μ¬νλλ°, κΈ°μ‘΄ Total κ³Ό μ
λ°μ΄νΈ λ Total μ€ μ΄λ€ κ°μ μνλμ§μ λ°λΌ β$5β κ° μ λ΅μ΄ λ μλ μκ³ ,
β$320β μ΄ μ λ΅μ΄ λ μλ μλ€. λ°λΌμ Conflicting Access
λ₯Ό κ³ μΉκΈ° μ μ μλμ΄ μννκ³ μ νλ μλλ₯Ό λͺ
νν νμ
νλ κ²μ΄ μ€μνλ€.
Concurrent Code λλ Multithreaded Code λ₯Ό μμ±ν κ²½μ°
Conflicting Access to Memory
λ₯Ό μμ£Ό μ ν μ μλ€. νμ§λ§ Conflicting Access λSingle Thread
μμλ λ°μν μ μλ€. μ΄ κΈμμ μ€λͺ νλ Conflicts κ° μ΄μ ν΄λΉνλ€.
- Conflicting Access to Memory (Single Thread) : Conflicts μ΄ λ°μν κ²½μ° Swift λ μ΄λ₯Ό κ°μ§ν΄ Compile-time Error λλ Runtime Error κ° λ°μνλλ‘ λ³΄μ₯νλ€.
- Conflicting Access to Memory (Multithread) : Thread Sanitizer λ₯Ό μ¬μ©ν΄ Threads μ¬μ΄μ λ°μνλ Conflicts μ κ°μ§νλ€.
2. Characteristics of Memory Access
Conflicting Access μμ κ³ λ €ν΄μΌ ν Memory Access μ 3κ°μ§ νΉμ±μ΄ μλ€.
- Read Access μΈκ°? Write Access μΈκ°?
- Access μ§μ μκ°
- Access λλ λ©λͺ¨λ¦¬ μμΉ
νΉν λ€μ 쑰건μ λ§μ‘±νλ 2κ°μ Accesses κ° μλ€λ©΄ Conflicts κ° λ°μνλ€.
- μ μ΄λ νλμ Write Access λλ Nonatomic Access
- λ©λͺ¨λ¦¬μ κ°μ μμΉμ μ κ·Ό
- μ κ·Ό κΈ°κ°(duration)μ΄ μ€λ³΅
μΌλ°μ μΌλ‘ Read Access μ Write Access μ μ°¨μ΄λ λͺ ννλ€. Write Access λ λ©λͺ¨λ¦¬μ μμΉλ₯Ό λ³κ²½νμ§λ§, Read Access λ κ·Έλ μ§ μλ€. λ©λͺ¨λ¦¬μ μμΉλ Variables, Constants, Properties μ κ°μ μ κ·Ό μ€μΈ νλͺ©μ λνλΈλ€. λ©λͺ¨λ¦¬ μ κ·Ό κΈ°κ°μ μκ°μ (instantaneous)μ΄κ±°λ μ₯κΈ°μ (long-term)μ΄λ€.
μ°μ°μ΄ C atomic operations λ§ μ¬μ©νλ κ²½μ° Atomic
μ΄κ³ , κ·Έλ μ§ μμΌλ©΄ Nonatomic
μ΄λ€. μ΄λ¬ν ν¨μ λͺ©λ‘μ stdatomic.3 νμ΄μ§λ₯Ό
μ°Έκ³ νλ€.
Access κ° μμλκ³ μ’
λ£λκΈ° μ κΉμ§ λ€λ₯Έ μ½λλ₯Ό μ€νν μ μλ κ²½μ°, μ κ·Όμ μ¦μ(instantaneous) μ΄λ£¨μ΄μ§λ€. μΌλ°μ μΌλ‘ 2κ°μ
Instantaneous Access
μ λμμ λ°μν μ μλ€. νμ§λ§ λλΆλΆμ λ©λͺ¨λ¦¬ μ κ·Όμ μ¦κ°μ μΌλ‘ λ°μνλ©°, μλ μ½λ 리μ€νΈμ λͺ¨λ Read Access
μ Write Access λ μ¦μ μ΄λ£¨μ΄μ§λ€(λμμ μ΄λ£¨μ΄μ§λ κ²μ λ§νλ κ²μ μλλ€. λμ΄ μμ°¨μ μΌλ‘ μ¦κ°μ μΈ λ°μμ 보μΈλ€λ κ²μ΄λ€).
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber) // 2
κ·Έλ¬λ λ€λ₯Έ μ½λμ μ€νμ κ±Έμ³ μλ Long-term Accesses μ μ κ·Όνλ λ°©λ²μ μ¬λ¬ κ°μ§κ° μλ€. Instantaneous Access
μ
Long-term Access
μ μ°¨μ΄μ μ Long-term Access λ μμλκ³ μ’
λ£λκΈ° μ μ λ€λ₯Έ μ½λκ° μ€νλ μ μλ€
λ κ²μ΄λ€.
μ΄κ²μ Overlap
μ΄λΌ νλ€.
- Instantaneous Access : Access κ° μμλκ³ μ’ λ£λκΈ° μ κΉμ§ λ€λ₯Έ μ½λκ° μ€νλ μ μλ€.
- Long-term Access :
Overlap
μ΄ κ°λ₯ν΄ Access κ° μμλκ³ μ’ λ£λκΈ° μ κΉμ§ λ€λ₯Έ Instantaneous Access λλ Long-term Access κ° μ€νλ μ μλ€.
Overlapping Accesses
λ μ£Όλ‘ ν¨μλ λ©μλμμin-out
λλmutating
μ μ¬μ©νλ μ½λμμ μ£Όλ‘ λνλλ€.
3. Conflicting Access to In-Out Parameters π©βπ»
ν¨μλ λͺ¨λ In-Out Parameters μ Long-term Write Access λ₯Ό κ°κ³ μλ€. In-Out Parameters μ λν Write Access λ
λλ¨Έμ§ λͺ¨λ Non-In-Out Parameters κ° νκ°λ νμ μμλμ΄ ν¨μκ° νΈμΆλλ λμ μ§μλλ€. In-Out Parameters κ° μ¬λ¬ κ°μΈ κ²½μ°
Write Access λ Parameters μ μμμ λμΌνκ² μ΄λ£¨μ΄μ§λ€.
- Read Access μ Write Access κ° λμμ μ΄λ£¨μ΄μ§μ§ μλ κ²½μ°
var someNumber = 7
func incrementByTen(_ number: inout Int) {
number += 10
}
incrementByTen(&someNumber)
print(someNumber) // 7
- Long-term Write Access λ₯Ό κ°λ In-Out Parameters μ ν¨μ λ΄λΆμ λ€λ₯Έ Read Access κ° λμμ μ΄λ£¨μ΄μ§ κ²½μ°(same duration)
var someNumber = 7
func incrementByTen(_ number: inout Int) {
print(someNumber) // error: simultaneous access
number += 10
}
incrementByTen(&someNumber) // error: Execution was interrupted, reason: signal SIGABRT.
print(someNumber)
λ€μκ³Ό κ°μ ν¨μλ₯Ό μκ°ν΄λ³΄μ.
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize) // error: Execution was interrupted, reason: signal SIGABRT.
μμμ μ΄ν΄λ³Έ κ²κ³Ό λ§μ°¬κ°μ§λ‘ Read Access μ Write Access κ° λμμ μ΄λ£¨μ΄μ§λ―λ‘ Conflicts κ° λ°μνλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νλ λ°©λ² μ€ ν κ°μ§λ In-Out Parameters
λ‘ μ λ¬λλ μλ³Έ λ°μ΄ν°κ° μ¬μ°Έμ‘°λμ§ μλλ‘ λͺ
ννκ² κ°μ 볡μ¬ν΄ μ λ¬νκ³ ,
ν¨μκ° μ’
λ£λ ν μλ³Έ κ°μ μ
λ°μ΄νΈ νλ κ²μ΄λ€.
var stepSize = 1
// Make an explicit copy.
var copyOfStepSize = stepSize
func increment(_ number: inout Int) {
number += stepSize
}
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
print(stepSize) // 2
κ·Έλ¦¬κ³ In-Out Parameters
λ₯Ό μ λ¬ν λ μΆκ°λ‘ μ£Όμν΄μΌ ν κ²μ, μ¬λ¬ κ°μ Parameters μ λμΌν λ³μλ₯Ό μ λ¬νλ κ²μ΄ κ°λ₯ν μΌλ°
Parameters μ λ¬λ¦¬ λμΌν λ³μλ₯Ό μ λ¬ν μ μλ€λ κ²μ΄λ€.
- μΌλ° Parameters λ λμΌν λ³μλ₯Ό 2κ°μ Parameters μ μ λ¬ν μ μλ€.
func balance(_ x: Int, _ y: Int) -> (Int, Int) {
let sum = x + y
return (sum / 2, sum - x)
}
var playerOneScore = 42
var playerTwoScore = 30
let (lhs1, rhs1): (Int, Int) = balance(playerOneScore, playerTwoScore)
let (lhs2, rhs2): (Int, Int) = balance(playerOneScore, playerOneScore)
print(lhs1, rhs1) // 36 30
print(lhs2, rhs2) // 42 42
In-Out Parameters
λ λμΌν λ³μλ₯Ό μ λ¬ν μ μλ€.
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
balance(&playerOneScore, &playerTwoScore)
λ λ κ°μ Parameters κ° λͺ¨λ Overlap
λμ§λ§, λ©λͺ¨λ¦¬μ λ€λ₯Έ μμΉμ μ κ·Όνλ―λ‘
Conflicts κ° λ°μνμ§ μλλ€.
λ°λ©΄, balance(&playerOneScore, &playerOneScore)
λ λ κ°μ Parameters κ° λμμ λ©λͺ¨λ¦¬μ κ°μ μμΉμ Write Access λ₯Ό
μννλ―λ‘ Conflicts κ° λ°μνλ€.
4. Conflicting Access to self in Methods π©βπ»
Structures μ mutating methods
λ λ©μλλ₯Ό νΈμΆνλ λμ self
μ λν Write Access λ₯Ό κ°λλ€.
κ° νλ μ΄μ΄λ λ°λ―Έμ§λ₯Ό μ μΌλ©΄ 체λ ₯μ΄ μ€μ΄λ€κ³ , νΉμ λ₯λ ₯μ μ¬μ©νλ©΄ μλμ§κ° μ€μ΄λλ κ²μμ΄ μλ€κ³ μκ°ν΄λ³΄μ.
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
restoreHealth()
λ©μλμ self
μ λν Write Access λ λ©μλμ νΈμΆμ μμλμ΄ λ°νλ λκΉμ§ μ μ§
λλ€. μ΄ λ©μλλ λ΄λΆμ
Player instance μ Properties μ Overlapping Access(μ€λ³΅ μ κ·Ό)
νλ λ€λ₯Έ μ½λλ μλ€.
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
νμ₯μΌλ‘ μΆκ°ν shareHealth(with:)
λ©μλλ In-Out Parameters λ‘ λ€λ₯Έ Player μ Instance λ₯Ό κ°μ§κ³ μμΌλ©°,
Overlapping Access μ κ·Όμ λν κ°λ₯μ±μ λ§λ λ€.
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // OK
print(oscar) // Player(name: "Oscar", health: 8, energy: 10)
print(maria) // Player(name: "Maria", health: 7, energy: 10)
μ μ½λμμ oscar μ mutating methods shareHealth(with:)
κ° κ°λ Write Access μ λμμ self
,
μ¦, oscar μκΈ° μμ μ΄κ³ , In-Out Parameters λ‘ μ λ¬λλ maria κ° κ°λ Write Access μ λμμ maria
μ΄κΈ° λλ¬Έμ Conflicts κ° λ°μνμ§ μλλ€.
κ·Έλ¬λ shareHealth(with:)
μ In-Out Parameters λ‘ oscar λ₯Ό μ λ¬νλ©΄ mutating methods μ self
μ
In-Out Parameters
κ° λμΌν oscar λ₯Ό λμμΌλ‘ Write Access λ₯Ό νκΈ° λλ¬Έμ λμμ κ°μ λ©λͺ¨λ¦¬λ₯Ό μ°Έμ‘°νκ³
Overlap λλ―λ‘ Conflicts κ° λ°μνλ€.
oscar.shareHealth(with: &oscar) // error: inout arguments are not allowed to alias each other
5. Conflicting Access to Properties π©βπ»
Structures, Tuples, Enumerations μ κ°μ Value Types
λ Structure μ Properties λλ
Tuple μ Elementsμ κ°μ κ°λ³ κ΅¬μ± κ°(individual constituent values)μΌλ‘ ꡬμ±λλ€.
μ΄κ²μ Value Types μ΄κΈ° λλ¬Έμ κ°μ μΌλΆκ° λ³κ²½λλ³ μ μ²΄κ° λ³κ²½λλ€.
μ¦, Properties μ€ νλμ Read Access λλ Write Access μ κ·Όμ νλ κ²μ
self
λ₯Ό ν΅ν μ κ·Όμ΄κΈ° λλ¬Έμ μ€μ λ‘ μ 체 κ°μ λν Read Access λλ Write Access λ₯Ό
μꡬνλ κ²κ³Ό κ°λ€.
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// error: conflicting access to properties of playerInformation
μ μμ μμ balance(_:_:)
λ₯Ό νΈμΆνλ κ²μ playerInformation μ Overlapping Write Accesses λ₯Ό νλ κ²μ΄λ―λ‘ Conflicts κ°
λ°μνλ€.
λ§μ½, λ€μκ³Ό κ°μ΄ Tuple μ μ΄μ©ν΄ νλμ In-Out Parameter λ‘ μ λ¬λλ©΄ Conflicts κ° λ°μνμ§ μλλ€.
func balance(_ player: inout (health: Int, energy: Int)) {
let sum = player.health + player.energy
player.health = sum / 2
player.energy = sum - player.health
}
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation)
print(playerInformation) // (health: 15, energy: 15)
μλ μ½λλ λ§μ°¬κ°μ§μ μ΄μ λ‘ Conflicts κ° λ°μνλ€.
var holly = Player(name: "Holly", health: 10, energy: 20)
balance(&holly.health, &holly.energy) // Error
print(holly)
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νλ λ°©λ² μ€ ν κ°μ§λ In-Out Parameters λ‘ μ λ¬λλ μλ³Έ λ°μ΄ν°λ₯Ό
Global Variable μ΄ μλ Local Variable λ‘ λ³κ²½νλ κ²μ΄λ€. κ·Έλ¬λ©΄ Swift compiler λ Structure μ
Stored Properties μ λν Access κ° λ€λ₯Έ μ½λμ λΆλΆκ³Ό μνΈμμ©νμ§ μμΌλ―λ‘ μμ νλ€λ κ²μ μ¦λͺ
ν μ μκ² λκ³ ,
2κ°μ In-Out Parameters κ° μ λ¬λμ§λ§ μ μμ μΌλ‘ μλν μ μλ€.
func someFunction() {
var holly = Player(name: "Holly", health: 10, energy: 20)
balance(&holly.health, &holly.energy)
print(holly)
}
someFunction()
Player(name: "Holly", health: 15, energy: 15)
μ μ½λμ λν΄ λ³΄μΆ© μ€λͺ μ νμλ©΄ λ€μκ³Ό κ°λ€.
Structure μ Properties μ λν μ€λ³΅ μ κ·Ό(Overlapping Access) μ νμ λ©λͺ¨λ¦¬ μμ μ±μ μν΄ νμ νμν κ²μ μλλ€. λ©λͺ¨λ¦¬ μμ μ±μ 보μ₯λμ§λ§,
λ°°νμ μ κ·Ό(exclusive access)
μλ©λͺ¨λ¦¬ μμ μ±(memory safety)
λ³΄λ€ λ μ격ν μꡬμ¬νμ΄λ€.μ¦, μΌλΆ μ½λλ λ©λͺ¨λ¦¬μ λν
Exclusive Access
λ₯Ό μλ°νλλΌλMemory Safety
λ₯Ό μ μ§νλ€λ κ²μ μλ―Ένλ€. μ΄λ μμ κ°μ΄ Swift compiler κ° λ©λͺ¨λ¦¬μ λνλΉλ°°νμ μ κ·Ό(nonexclusive access)
κ° μ¬μ ν μμ νλ€λ κ²μ μ¦λͺ ν μ μλMemory Safety
λ₯Ό νμ©νλ€.
Swift compiler μ μν΄ λ©λͺ¨λ¦¬μ λν
Nonexclusive Access
κ°Memory Safety
λ₯Ό κ°μ§ μ μλ 쑰건μ λ€μκ³Ό κ°λ€.
- μ€μ§ Instance μ
Stored Properties μλ§ μ κ·Ό
ν΄μΌνλ€(not Computed Properties or Class Properties).- Structure κ°
Local Variable
μ κ°μ΄μΌνλ€(not Global Variable).- Structure λ
μ΄λ€ Closures μλ μΊ‘μ²λμ§ μκ±°λ
orNonescaping Closures μ μν΄μλ§ μΊ‘μ²
λμ΄μΌνλ€. (μΌλ° Closures λλ Escaping Closures λ ν¨μ context μΈλΆμ μνΈμμ©μ νλ―λ‘ μμ ν 격리 λμ§ μλλ€.)
Reference
- βMemory Safety.β The Swift Programming Language Swift 5.7. accessed Mar. 13, 2023, Swift Docs Chapter 25 - Memory Safety.