24 Memory Safety 內(nèi)存安全

默認(rèn)情況下,Swift可以防止代碼中發(fā)生不安全行為曙旭。例如别垮,Swift確保變量在使用前被初始化便监,內(nèi)存被釋放后不會(huì)被訪問(wèn),并且檢查數(shù)組索引是否有越界錯(cuò)誤宰闰。

Swift還要求修改內(nèi)存中某個(gè)位置的代碼具有對(duì)該內(nèi)存的獨(dú)占訪問(wèn)權(quán)茬贵,從而確保對(duì)同一內(nèi)存區(qū)域的多次訪問(wèn)不會(huì)發(fā)生沖突。因?yàn)镾wift是自動(dòng)管理內(nèi)存的移袍,所以大多數(shù)時(shí)候你根本不用考慮訪問(wèn)內(nèi)存解藻。但是,了解可能發(fā)生沖突的地方很重要葡盗,這樣您就可以避免編寫訪問(wèn)內(nèi)存沖突的代碼螟左。如果您的代碼確實(shí)包含沖突,則會(huì)得到編譯時(shí)或運(yùn)行時(shí)錯(cuò)誤觅够。

Understanding Conflicting Access to Memory

當(dāng)您設(shè)置變量的值或?qū)?shù)傳遞給函數(shù)時(shí)胶背,對(duì)內(nèi)存的訪問(wèn)發(fā)生在代碼中。例如喘先,下面的代碼包含讀訪問(wèn)和寫訪問(wèn):

// 對(duì)存儲(chǔ)1的內(nèi)存進(jìn)行寫訪問(wèn)
var one = 1

//對(duì)存儲(chǔ)1的內(nèi)存進(jìn)行讀訪問(wèn)
print("We're number \(one)!")

當(dāng)代碼的不同部分試圖同時(shí)訪問(wèn)內(nèi)存中的相同位置時(shí)钳吟,可能會(huì)發(fā)生對(duì)內(nèi)存的沖突訪問(wèn)。同時(shí)多次訪問(wèn)內(nèi)存中的某個(gè)位置可能會(huì)產(chǎn)生不可預(yù)測(cè)或不一致的行為窘拯。在Swift中红且,有幾種方法可以修改跨越幾行代碼的值,從而使在修改過(guò)程中訪問(wèn)值成為可能涤姊。

memory_shopping_2x.png

當(dāng)您向預(yù)算添加項(xiàng)目時(shí)暇番,它處于臨時(shí)的無(wú)效狀態(tài),因?yàn)榭偨痤~沒(méi)有更新以反映新添加的項(xiàng)目思喊。在添加項(xiàng)目的過(guò)程中讀取總金額會(huì)給出不正確的信息壁酬。

這個(gè)示例還演示了在修復(fù)對(duì)內(nèi)存的沖突訪問(wèn)時(shí)可能遇到的一個(gè)挑戰(zhàn):有時(shí)有多種方法可以修復(fù)產(chǎn)生不同答案的沖突,而且并不總是很明顯哪個(gè)答案是正確的恨课。在本例中舆乔,根據(jù)需要原始總額還是更新后的總額,正確的答案可能是5或320庄呈。在修復(fù)沖突訪問(wèn)之前蜕煌,您必須確定它的目的是什么。

如果您已經(jīng)編寫了并發(fā)或多線程代碼诬留,那么對(duì)內(nèi)存的沖突訪問(wèn)可能是一個(gè)常見(jiàn)的問(wèn)題斜纪。然而贫母,這里討論的沖突訪問(wèn)可能發(fā)生在單個(gè)線程上,并且不涉及并發(fā)或多線程代碼盒刚。

如果你在一個(gè)線程中有沖突的內(nèi)存訪問(wèn)腺劣,Swift保證你會(huì)在編譯時(shí)或運(yùn)行時(shí)得到一個(gè)錯(cuò)誤。對(duì)于多線程代碼因块,使用線程殺毒器來(lái)幫助檢測(cè)線程之間的沖突訪問(wèn)橘原。

Characteristics of Memory Access 內(nèi)存訪問(wèn)特性

在訪問(wèn)沖突的上下文中,需要考慮內(nèi)存訪問(wèn)的三個(gè)特征:訪問(wèn)是讀還是寫涡上、訪問(wèn)的持續(xù)時(shí)間和正在訪問(wèn)的內(nèi)存中的位置趾断。具體地說(shuō),如果您有兩個(gè)符合以下所有條件的訪問(wèn)吩愧,則會(huì)發(fā)生沖突:

  • 至少有一個(gè)是寫訪問(wèn)芋酌。
  • 它們?cè)L問(wèn)內(nèi)存中的相同位置。
  • 他們的時(shí)間重疊雁佳。

讀訪問(wèn)和寫訪問(wèn)之間的區(qū)別通常很明顯:寫訪問(wèn)會(huì)更改內(nèi)存中的位置脐帝,而讀訪問(wèn)不會(huì)。內(nèi)存中的位置指的是正在訪問(wèn)的內(nèi)容——例如糖权,變量堵腹、常量或?qū)傩浴?nèi)存訪問(wèn)的持續(xù)時(shí)間可以是瞬時(shí)的星澳,也可以是長(zhǎng)期的疚顷。

如果其他代碼無(wú)法在訪問(wèn)開始之后而在訪問(wèn)結(jié)束之前運(yùn)行,則訪問(wèn)是瞬時(shí)的禁偎。從本質(zhì)上講荡含,兩個(gè)瞬時(shí)訪問(wèn)不可能同時(shí)發(fā)生。大多數(shù)內(nèi)存訪問(wèn)都是瞬時(shí)的届垫。例如,下面代碼清單中的所有讀寫訪問(wèn)都是瞬時(shí)的:

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

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

然而全释,有幾種訪問(wèn)內(nèi)存的方法装处,稱為長(zhǎng)期訪問(wèn),它們跨越了其他代碼的執(zhí)行浸船。瞬時(shí)訪問(wèn)和長(zhǎng)期訪問(wèn)的區(qū)別在于妄迁,其他代碼可能在長(zhǎng)期訪問(wèn)開始后運(yùn)行,但在長(zhǎng)期訪問(wèn)結(jié)束之前運(yùn)行李命,這稱為重疊登淘。長(zhǎng)期訪問(wèn)可以與其他長(zhǎng)期訪問(wèn)和瞬時(shí)訪問(wèn)重疊。

重疊訪問(wèn)主要出現(xiàn)在在函數(shù)和方法中使用in-out參數(shù)的代碼中封字,或者在結(jié)構(gòu)的方法中進(jìn)行修改的代碼中黔州。下面將討論使用長(zhǎng)期訪問(wèn)的特定類型的Swift代碼耍鬓。

Conflicting Access to In-Out Parameters 輸入輸出參數(shù)的訪問(wèn)沖突

函數(shù)具有對(duì)其所有in-out參數(shù)的長(zhǎng)期寫訪問(wèn)權(quán)。in-out參數(shù)的寫訪問(wèn)在所有非in-out參數(shù)被評(píng)估之后開始流妻,并持續(xù)到函數(shù)調(diào)用的整個(gè)期間牲蜀。如果有多個(gè)in-out參數(shù),那么寫入訪問(wèn)將按照參數(shù)出現(xiàn)的順序開始绅这。

這種長(zhǎng)期寫訪問(wèn)的一個(gè)結(jié)果是涣达,您不能訪問(wèn)作為in-out傳遞的原始變量,即使范圍規(guī)則和訪問(wèn)控制允許這樣做—對(duì)原始變量的任何訪問(wèn)都會(huì)產(chǎn)生沖突证薇。例如:

var stepSize = 1

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

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

在上面的代碼中度苔,stepSize是一個(gè)全局變量,通郴攵龋可以從increment(_:)中訪問(wèn)它寇窑。但是,對(duì)stepSize的讀訪問(wèn)與對(duì)number的寫訪問(wèn)重疊俺泣。如下圖所示疗认,number和stepSize都指向內(nèi)存中的相同位置。讀和寫訪問(wèn)指的是相同的內(nèi)存伏钠,它們重疊横漏,產(chǎn)生沖突。

memory_increment_2x.png

解決這個(gè)沖突的一個(gè)方法是顯式復(fù)制stepSize:

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

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

當(dāng)您在調(diào)用increment(_:)之前復(fù)制stepSize時(shí)熟掂,很明顯copyOfStepSize的值是由當(dāng)前的步驟大小遞增的缎浇。讀訪問(wèn)在寫訪問(wèn)開始之前結(jié)束,因此不存在沖突赴肚。

對(duì)in-out參數(shù)的長(zhǎng)期寫訪問(wèn)的另一個(gè)結(jié)果是素跺,將單個(gè)變量作為同一函數(shù)的多個(gè)in-out參數(shù)的參數(shù)傳遞會(huì)產(chǎn)生沖突。例如:

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(::)函數(shù)修改了它的兩個(gè)參數(shù)誉券,以便在它們之間平均分配總價(jià)值指厌。用playerOneScore和playerTwoScore作為參數(shù)調(diào)用它不會(huì)產(chǎn)生沖突——有兩個(gè)寫訪問(wèn)在時(shí)間上重疊,但是它們?cè)L問(wèn)內(nèi)存中的不同位置踊跟。相反踩验,將playerOneScore作為兩個(gè)參數(shù)的值傳遞會(huì)產(chǎn)生沖突,因?yàn)樗噲D同時(shí)執(zhí)行對(duì)內(nèi)存中相同位置的兩次寫訪問(wèn)商玫。

因?yàn)椴僮鞣呛瘮?shù)箕憾,所以它們也可以長(zhǎng)期訪問(wèn)它們的in-out參數(shù)。例如拳昌,如果balance(::)是一個(gè)名為<^>的操作符函數(shù)袭异,那么編寫playerOneScore <^> playerOneScore將導(dǎo)致與balance(&playerOneScore, &playerOneScore)相同的沖突炬藤。

Conflicting Access to self in Methods 方法中對(duì)self的沖突訪問(wèn)

結(jié)構(gòu)上的修改方法在方法調(diào)用期間具有對(duì)self的寫訪問(wèn)權(quán)御铃。例如碴里,考慮一個(gè)游戲,每個(gè)玩家都有一個(gè)生命值(在受到傷害時(shí)降低)和一個(gè)能量值(在使用特殊技能時(shí)降低)畅买。

struct Player {
    var name: String
    var health: Int
    var energy: Int

    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}

在上面的restoreHealth()方法中并闲,對(duì)self的寫訪問(wèn)從方法的開頭開始,一直持續(xù)到方法返回谷羞。在本例中帝火,restoreHealth()中沒(méi)有其他代碼可以對(duì)Player實(shí)例的屬性進(jìn)行重疊訪問(wèn)。下面的shareHealth(with:)方法將另一個(gè)Player實(shí)例作為in-out參數(shù)湃缎,從而創(chuàng)建了重疊訪問(wèn)的可能性犀填。

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的玩家調(diào)用shareHealth(with:)方法來(lái)與Maria的玩家共享health不會(huì)引起沖突嗓违。在方法調(diào)用期間有對(duì)oscar的寫訪問(wèn)九巡,因?yàn)閛scar是在一個(gè)可變方法中self的值,在相同的時(shí)間內(nèi)有對(duì)maria的寫訪問(wèn)蹂季,因?yàn)閙aria是作為in-out參數(shù)傳遞的冕广。如下圖所示,它們?cè)L問(wèn)內(nèi)存中的不同位置偿洁。即使這兩個(gè)寫訪問(wèn)在時(shí)間上重疊撒汉,它們也不會(huì)沖突。

memory_share_health_maria_2x.png

但是涕滋,如果將oscar作為參數(shù)傳遞給shareHealth(with:)睬辐,就會(huì)產(chǎn)生沖突:

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

在方法的持續(xù)時(shí)間內(nèi),修改方法需要對(duì)self的寫訪問(wèn)權(quán)宾肺,而in-out參數(shù)在相同的持續(xù)時(shí)間內(nèi)需要對(duì)teamate的寫訪問(wèn)權(quán)溯饵。在方法中,self和team都引用內(nèi)存中的相同位置锨用,如下圖所示丰刊。這兩個(gè)寫訪問(wèn)指的是相同的內(nèi)存,它們重疊增拥,產(chǎn)生沖突藻三。

memory_share_health_oscar_2x.png

Conflicting Access to Properties 屬性訪問(wèn)沖突

結(jié)構(gòu)、元組和枚舉等類型由單獨(dú)的組成值組成跪者,例如結(jié)構(gòu)的屬性或元組的元素。因?yàn)檫@些是值類型熄求,所以對(duì)值的任何部分進(jìn)行修改都會(huì)對(duì)整個(gè)值進(jìn)行修改渣玲,這意味著對(duì)其中一個(gè)屬性的讀或?qū)懺L問(wèn)需要對(duì)整個(gè)值進(jìn)行讀或?qū)懺L問(wèn)。例如弟晚,對(duì)元組元素的重疊寫訪問(wèn)會(huì)產(chǎn)生沖突:

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

在上面的例子中忘衍,對(duì)元組的元素調(diào)用balance(::)會(huì)產(chǎn)生沖突逾苫,因?yàn)閷?duì)playerInformation有重疊的寫訪問(wèn)。playerInformation.health 和 playerInformation.energy作為in-out參數(shù)傳遞枚钓,這意味著balance(::)在函數(shù)調(diào)用期間需要對(duì)它們進(jìn)行寫訪問(wèn)铅搓。在這兩種情況下,對(duì)元組元素的寫訪問(wèn)都需要對(duì)整個(gè)元組的寫訪問(wèn)搀捷。這意味著有兩種對(duì)playerInformation的寫訪問(wèn)星掰,它們的持續(xù)時(shí)間重疊,從而導(dǎo)致沖突嫩舟。

下面的代碼顯示氢烘,對(duì)存儲(chǔ)在全局變量中的結(jié)構(gòu)的屬性的重疊寫訪問(wèn)也會(huì)出現(xiàn)相同的錯(cuò)誤。

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

實(shí)際上家厌,對(duì)結(jié)構(gòu)屬性的大多數(shù)訪問(wèn)都可以安全地重疊播玖。例如,如果將上面例子中的變量holly改為局部變量而不是全局變量饭于,編譯器可以證明對(duì)結(jié)構(gòu)存儲(chǔ)屬性的重疊訪問(wèn)是安全的:

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

在上面的例子中蜀踏,Oscar的健康和精力被傳遞為兩個(gè)In -out參數(shù)來(lái)平衡(::)。編譯器可以證明內(nèi)存安全得到了保護(hù)掰吕,因?yàn)檫@兩個(gè)存儲(chǔ)的屬性沒(méi)有以任何方式交互果覆。

限制對(duì)結(jié)構(gòu)屬性的重疊訪問(wèn)并不總是保持內(nèi)存安全所必需的。內(nèi)存安全是需要的保證畴栖,但是獨(dú)占訪問(wèn)比內(nèi)存安全要求更嚴(yán)格——這意味著一些代碼保留內(nèi)存安全随静,即使它違反了對(duì)內(nèi)存的獨(dú)占訪問(wèn)。如果編譯器能夠證明對(duì)內(nèi)存的非排他性訪問(wèn)仍然是安全的吗讶,Swift就允許使用這種內(nèi)存安全代碼燎猛。具體來(lái)說(shuō),如果符合以下條件照皆,則可以證明對(duì)結(jié)構(gòu)屬性的重疊訪問(wèn)是安全的:

  • 您只訪問(wèn)實(shí)例的存儲(chǔ)屬性重绷,而不訪問(wèn)計(jì)算屬性或類屬性。
  • 結(jié)構(gòu)是局部變量的值膜毁,而不是全局變量昭卓。
  • 結(jié)構(gòu)要么不被任何閉包捕獲,要么只被非轉(zhuǎn)義閉包捕獲瘟滨。

如果編譯器不能證明訪問(wèn)是安全的候醒,它就不允許訪問(wèn)。

<<返回目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杂瘸,一起剝皮案震驚了整個(gè)濱河市倒淫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌败玉,老刑警劉巖敌土,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜硕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡返干,警方通過(guò)查閱死者的電腦和手機(jī)兴枯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)矩欠,“玉大人财剖,你說(shuō)我怎么就攤上這事⊥砬辏” “怎么了峰伙?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)该默。 經(jīng)常有香客問(wèn)我瞳氓,道長(zhǎng),這世上最難降的妖魔是什么栓袖? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任匣摘,我火速辦了婚禮,結(jié)果婚禮上裹刮,老公的妹妹穿的比我還像新娘音榜。我一直安慰自己,他們只是感情好捧弃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布赠叼。 她就那樣靜靜地躺著,像睡著了一般违霞。 火紅的嫁衣襯著肌膚如雪嘴办。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天买鸽,我揣著相機(jī)與錄音涧郊,去河邊找鬼。 笑死眼五,一個(gè)胖子當(dāng)著我的面吹牛妆艘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播看幼,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼批旺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诵姜?” 一聲冷哼從身側(cè)響起朱沃,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后逗物,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑟俭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年翎卓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摆寄。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡失暴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出微饥,到底是詐尸還是另有隱情逗扒,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布欠橘,位于F島的核電站矩肩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肃续。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荠列。 院中可真熱鬧撒遣,春花似錦、人聲如沸瞧捌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姐呐。三九已至殿怜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間皮钠,已是汗流浹背稳捆。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留麦轰,地道東北人乔夯。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像款侵,于是被迫代替她去往敵國(guó)和親末荐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 默認(rèn)情況下新锈,Swift 會(huì)阻止你代碼里不安全的行為甲脏。例如,Swift 會(huì)保證變量在使用之前就完成初始化,在內(nèi)存被回...
    答案MK閱讀 250評(píng)論 0 0
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,100評(píng)論 1 32
  • 默認(rèn)情況下块请,Swift可以防止代碼中出現(xiàn)不安全行為娜氏。例如,Swift確保變量在使用之前被初始化墩新,內(nèi)存在被釋放后不被...
    WSJay閱讀 1,692評(píng)論 1 7
  • 道學(xué)之形成 本書所稱“道學(xué)”贸弥,就是我們所稱的宋明理學(xué)又稱宋學(xué),理學(xué)海渊,性理學(xué)绵疲,程朱學(xué),濂洛關(guān)閩之學(xué)臣疑,書中梳理了自北宋...
    成人的道閱讀 1,065評(píng)論 0 0
  • the1special閱讀 313評(píng)論 0 1