BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Swift 5ではメモリの排他アクセスが必須になる

Swift 5ではメモリの排他アクセスが必須になる

原文(投稿日:2019/02/08)へのリンク

Swift 5では,変数がプログラムの他の部分で変更されている間,別の名称を使ってアクセスできないようにすることで,Swiftプログラムのメモリ安全性が向上している。この変更は,既存のアプリの振る舞いにも,Swiftコンパイラ自身にも重大な影響を及ぼす。

メモリへの排他アクセスの問題は,さまざまな状況で現れる。多くはコンパイラが静的にキャッチ可能だが,ランタイム時にのみ処理可能なケースも存在する。クロージャのエスケープによる排他侵害,クラス型プロパティ,静的プロパティ,グローバル変数などがそれに含まれる。

問題を説明するために,一般的なケースについて考えてみよう。関数のinout引数として変更される変数が,その関数内で実行されるクロージャの引数としても使用されることで,同じ変数が同一スコープ内で,2つの異なる名称でアクセスされる場合である。

func modifyTwice(_ value: inout Int, by modifier: (inout Int) -> ()) {
  modifier(&value)
  modifier(&value)
}

func testCount() {
  var count = 1
  modifyTwice(&count) { $0 += count }
  print(count)
}

この例では,modifyTwiceinput引数として使用されているcountが,同時にmodifierでも使用されることによって問題が発生する。この影響として,print文で何が出力されるべきなのかが不明確になっている。countが最初にインクリメントされた時,その値は2に増加する。では,2回目の加算が実行された時,$0の値に加算されるcountの値は何だろうか?メモリ操作は必ずしも即時に実行されないので,これにはさまざまな要因が関わってくる。さらに悪いことに,コンパイラが最適化を行うことで,このようなシナリオをますます複雑にする可能性があるのだ。

この問題は,前述のような,異なる変数名によるメモリの同時変更の予測不能性だけではなく,これがコンパイラに課す複雑さにも関係している。

これは予期しない,混乱を招く結果をもたらす可能性があります。それと同時に,異常な状況下でもプログラムの基本的健全性(クラッシュや未定義動作を行わないこと)を保証するために,コンパイラや標準ライブラリの実装に対しても,極めて保守的な動作を強いることになります。

これはすべて,Swift 5を使用してコンパイルされたアプリケーションは,排他アクセス違反があった場合は実行時にクラッシュする,という意味になる。この動作は従来,Swift 4コンパイラのデバッグモードでは使用できていた。従って,Runtimeモードでのみテストされたプログラムについては,Swift 5でコンパイルするとクラッシュする危険がある。

排他アクセス違反を修正するための一般的なアプローチは,データのコピーを作ることだ。先程の例であれば,次のようになる。

func modifyTwice(_ value: inout Int, by modifier: (inout Int) -> ()) {
  modifier(&value)
  modifier(&value)
}

func testCount() {
  var count = 1
  let increment = count
  modifyTwice(&count) { $0 += increment }
  print(count)
}

このような問題が起きた場合,排他アクセス違反チェックを無効にするという方法もあるが,このプラクティスは決して推奨できない。

ランタイムチェックを無効にすればパフォーマンス低下を回避できるかも知れませんが,排他違反が安全であるということにはなりません。チェックが無効になれば,排他ルールの遵守はプログラマの責任になるのです。

詳細や他の例については,オリジナルの記事を参照してほしい。

この記事に星をつける

おすすめ度
スタイル

BT