Swift Memory Safty Swift 內(nèi)存安全機(jī)理(亂譯)

前言

亂譯:不是規(guī)規(guī)矩矩的翻譯,主要目的是為了學(xué)知識(shí)。但也是無奈之舉,水平有限,我不會(huì)啊。有些地方我加入了自己的理解,關(guān)于新的知識(shí)點(diǎn)我會(huì)翻譯的詳細(xì)些,舊知識(shí)我就概括翻譯,不懂的就不翻譯。
官網(wǎng) : Memory Safety

Beginning

默認(rèn)情況下,Swift 會(huì)防止不安全的編碼行為發(fā)生。例如:
1 .確保變量在初始化之后才能使用
2 .內(nèi)存(對(duì)象)釋放 之后,是不可訪問的
3 .數(shù)組下標(biāo)會(huì)做越界檢測(cè)

Swift 可以確保同一內(nèi)存多個(gè)訪問不發(fā)生沖突。是這樣做到的:
把一個(gè)內(nèi)存地址改到一個(gè)唯一的地址。 啥,啥,啥!不會(huì)翻譯,好像是說,每一個(gè)訪問的代碼在修改對(duì)象之前,都會(huì)修改訪問對(duì)象的地址到一個(gè)只有它自己可以訪問的唯一的地址。 這樣就不沖突了(后面發(fā)現(xiàn)它這是針對(duì)單線程的,一開始,誤以為是多線程讀到這嚇一跳)

Swift 的這些內(nèi)存管理都是自動(dòng)的,如果我們的代碼包含了沖突,會(huì)有編譯時(shí)錯(cuò)誤或運(yùn)行時(shí)錯(cuò)誤,但是我們應(yīng)知其然知其所以然。這就是這篇文章的目的。

Understanding Conflicting Access to Memory 理解內(nèi)存訪問沖突

當(dāng)我們給變量賦值或向函數(shù)傳參時(shí),就會(huì)發(fā)生內(nèi)存訪問。如下代碼包含了內(nèi)存讀訪問和寫訪問

// one 被寫入內(nèi)存
var one = 1
// one 從內(nèi)存中讀出來
print("We're number \(one)!")

下面舉個(gè)了賬單的讀寫沖突的例子,舊知識(shí),老生常談的,不翻譯了,。。。

note
這里討論的是單線程的情況,不包含并發(fā)編程和多線程情況。
如果你是在單線程時(shí),寫了內(nèi)存訪問沖突的代碼,Swift會(huì)自動(dòng)的在編譯或運(yùn)行時(shí)報(bào)錯(cuò)。
如果你多線程編程請(qǐng)使用 Thread Sanitizer 幫助你檢測(cè)線程沖突。

Characteristics of Memory Access 內(nèi)存訪問的特征

相當(dāng)于介紹了內(nèi)存訪問:問自己什么是內(nèi)存訪問,便于理解。

  • 至少一個(gè)在讀
  • 多個(gè)同時(shí)寫
  • 他們期間,發(fā)生重疊。讀的期間,寫的期間。

想一想,有些讀寫是瞬間的有些并不是瞬間的,不能理解看完后面的部分,就會(huì)明白

訪問有瞬間訪問和長期訪問之分,當(dāng)然,瞬間訪問是不會(huì)沖突。大部分內(nèi)存訪問都是瞬間訪問,例如下面的例子

func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"

但是,重點(diǎn)!重點(diǎn)!重點(diǎn)!有幾種方式的內(nèi)存訪問是長期訪問,在這個(gè)期間,其他代碼可以執(zhí)行。

和瞬間訪問不同,對(duì)長期訪問來說,其他代碼是可以在長期訪問開始但沒有結(jié)束期間執(zhí)行的。我們把這叫做 overlap 覆蓋

長期訪問最基本的一個(gè)例子是在使用 in-out 參數(shù)時(shí)。

Conflicting Access to In-Out Paramters In-Out 參數(shù)訪問沖突

函數(shù)對(duì)它所有的 in-out 參數(shù) 持有長期寫的訪問,持有開始于非 in-out 參數(shù)的 evaluated (啥啥啥,不會(huì)翻譯了,就記住從函數(shù)開始到函數(shù)結(jié)束)(這是Swift語言決定的,我說的哈哈)

長期寫訪問的一個(gè)結(jié)論是 :你不能訪問通過 in-out 修飾傳過來的參數(shù),即使它本來可以訪問。例如下面的例子,你一運(yùn)行就 crash 報(bào)錯(cuò): Simultaneous accesses to 0x100006728, but modification requires exclusive access.

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&stepSize)
// Error: conflicting accesses to stepSize

雖然 stepSize is a global variable, 本來可以訪問,但這里不能訪問了!看下圖很容易理解


1547091660951.jpg

一種解決方案是:對(duì) setpSize 顯示 copy

// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)

// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2

長期寫訪問的另一個(gè)關(guān)于 in-out 參數(shù)的結(jié)論是:不可以對(duì)有多個(gè) in-out 參數(shù)的函數(shù)傳遞一個(gè)變量??蠢泳兔靼滓馑剂?/p>

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

Conflicting Access to self in Methods 方法中 self 的訪問沖突
結(jié)構(gòu)體的可變方法在被調(diào)用期間對(duì) self 持有讀的訪問。

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

如果,你把 oscar 作為參數(shù)傳給 sheareHealth(with:),會(huì)有沖突:

oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar

shareHealth 對(duì) self 有讀的訪問,in-out 對(duì) teammate 有讀的訪問。self 和 teammate 都是 oscar 產(chǎn)生 沖突!這一點(diǎn)原文的圖好像畫的有點(diǎn)問題,文字表述是對(duì)的。

Conflicting Access to Properties 屬性訪問沖突
對(duì)于值類型像 structures,tuple,enumerations 他們是由單個(gè)的值組成。任何一個(gè)屬性的修改,都會(huì)對(duì)整個(gè)值持有讀或?qū)懙脑L問。例如下面會(huì)產(chǎn)生些訪問沖突

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation

同時(shí)持有 playerInfomation


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

同時(shí)持有 holly

但是在實(shí)際開發(fā)中,有時(shí)結(jié)構(gòu)體屬性的覆蓋訪問是安全的。如下例子:

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

上面這個(gè)例子中,oscar 是局部變量 health 和 energy 這兩個(gè)儲(chǔ)屬性沒有在任何地方交互,所以編譯器判斷這個(gè)內(nèi)存覆蓋訪問是安全的。

Memory safety 的目的是為了保證內(nèi)存訪問的安全,但是唯一訪問的要求更加的嚴(yán)格。Swift 允許非唯一訪問內(nèi)存,只要它能夠證明內(nèi)存是安全的。
對(duì)于結(jié)構(gòu)體,滿足下面條件的就可以證明是安全的

  • 你正在訪問的的只有實(shí)例的存儲(chǔ)屬性,不包括計(jì)算屬性和類屬性
  • 結(jié)構(gòu)體是局部變量
  • 結(jié)構(gòu)體沒有被閉包捕獲,或者被非逃逸閉包捕獲。 (捕獲就是放在閉包你面,我說的哈哈)

如果編譯器不能證明訪問是安全的,它就不允許訪問,就會(huì)報(bào)錯(cuò)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容