31316


ポインタの勉強中です。

[code]

    class BaseClass{
        var value : Int
        init(_ a:Int){
            self.value = a
        }
        deinit{
            print("BaseClass:\(self.value)")
        }
    
    }
    
    func ptr2<T: Any>(p: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<T> {
        return p
    }
    
    do{
        var i = BaseClass(100)
        var p = ptr2(p:&i)
        print(type(of:p))
    
        var array = UnsafeMutableBufferPointer<BaseClass>(start:p,count:100)
        print(type(of:array))
        print(array.count)
        array[1] = BaseClass(200)
    }
    
[/code]

array[1] = BaseClass()が、解放されていません  
なぜでしょうか？


CC BY-SA 3.0
2016-12-22T07:25:22.337
31315
2016-12-22T11:17:22.307
11148
question
swift swift3
UnsafeBufferPointer<T>で確保した領域の解放？？


本題に入る前にSwiftでのポインタの学習をされるのであれば絶対に知っておいていただきたい点を先に述べておきます。

それは、

[code]

    func ptr2<T: Any>(p: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<T> {
        return p
    }
    
[/code]

のような形でinoutパラメータとして渡されたポインタを関数の終了後も使用するのは、Swiftでは **やってはいけない** の一つであるということです。

Using本([Using Swift with Cocoa and Objective-C (Swift
3.0.1)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)の[Pointers](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-ID23)の部分にこのように明記されています。

> The pointer passed to the function is guaranteed to be valid only for  
>  the duration of the function call. Don’t try to persist the pointer  
>  and access it after the function has returned.
>
>
> このようにして渡されたポインタが有効なのは関数の実行中だけです。そのようなポインタを関数外の変数に代入したり戻り値として返したりして関数から戻った後でアクセスしようとするのは
> **絶対ダメ** です。

通常「してはいけない」を"you should not"で表現する技術文書で"Don't
try"と命令形を使っているのにはかなり強く禁止するというニュアンスが含まれるため **絶対ダメ**
と訳しておきました。(`persist`については意訳してあります。)

残念ながらあなたが掲載されたようなコードをC言語の"address
of"演算子(`&`)代わりに使えるものとして喧伝している困ったブログ記事等もちょくちょく見かけますが、Swiftに関する公式ドキュメントが乏しかった頃なら仕方ないかもしれませんが(私も1年かそこら前に書いたコードに似た書き方が残っているのを見て冷や汗かきながら修正することがあります)、Appleが公式ドキュメントにはっきり
**絶対ダメ** と書いてあることをやるべきではないでしょう。

* * *

## と言うわけで本題です。

**絶対ダメ** の`ptr2`関数を使ったまま話はしたくないのですが、そこを書き換えるとポインタやARC絡みの別の問題が発生するため、ここでは

  * ブロックローカルの変数`i`はスタック上に割り当てられ、`p`はそのアドレスを指している

と言うことにしておきます。(コンパイラの最適化レベルを上げれば、小さな関数のローカル変数なんかは全てレジスタに割り当てられます。とても妥当な仮定とは言えないのですが。)

簡単にまとめると:

  * `UnsafeMutableBufferPointer(start:count:)`は自前で領域を確保することはありません。
  * `UnsafeMutableBufferPointer`型の変数(あなたのコード例の`array`)が指す領域が変数の有効期限の終わりに解放されることもありません。

`UnsafeMutableBufferPointer(start:count:)`の`start:`パラメータに`count:`で示した個数の領域を確保したポインタを渡すのはプログラマ側の仕事で、あなたのコードでは(上記の仮定が成立するとしても)変数`i`1個分の領域しか保証されないポインタを`count:100`で渡していますから、
**`array[1]`から`array[99]`までは一体どこを指しているのかわからない**状態、後はもう何が起こってもおかしくないところです。  
(運が良ければ、`array[1] =
BaseClass(200)`の実行時にcrashして、そこがおかしいとわかるのですが、ポインタ絡みのバグは原因となる場所は通過して後で発覚することも珍しくはありません。だからこそのSwiftでは全てのポインタ型が`Unsafe`のネーミングをされているわけです。)

* * *

ところで`UnsafeMutableBufferPointer<BaseClass>(start:p,count:100)`が正しく100個分の領域を持つようにするには、上に書いたように領域を確保した上でそのポインタを`start:`に渡さないといけないので、次のような書き方になります。

[code]

    do {
        let p = UnsafeMutablePointer<BaseClass?>.allocate(capacity: 100)
        p.initialize(to: nil, count: 100)
        let bufPtr = UnsafeMutableBufferPointer(start:p, count: 100)
        bufPtr[0] = BaseClass(100)
        bufPtr[1] = BaseClass(200)
        //...
        print("deinitialize starts")
        p.deinitialize(count: 100) //<-BaseClassのインスタンスが解放されるのはここ
        print("deinitialize finished")
        p.deallocate(capacity: 100)
        print("deallocate finished")
    }
    /*↓
    deinitialize starts
    BaseClass:100
    BaseClass:200
    deinitialize finished
    deallocate finished
    Program ended with exit code: 0
     */
    
[/code]

ここで注意して欲しいのは`bufPtr`を通じて代入した2つのインスタンスが解放されるのは`deallocate(capacity:)`の実行時ではなく、`deinitialize(count:)`の実行時だということです。参照型などのプリミティブとは言えない型を指すポインタを自前で確保する場合は、このように`allocate`,
`initialize`, `deinitialize`,
`deallocate`の4個セットを正しい順序で正しく実行してやらないとメモリリークや暴走・crashの原因になります。ご注意ください。


CC BY-SA 3.0
2016-12-22T11:04:54.167
31316
2016-12-22T11:17:22.307
2020-06-17T08:14:45.997
-1
13972
31315
answer