49411


■環境  
Mac/High Sierra 10.13.6/swift4.2/xcode 10.0

iosアプリで音を取得して解析することを繰り返すアプリを作成しようと思い、  
<https://qiita.com/a_jike/items/68dd13879f9df5b2b7a2>  
上記urlサイトを参考にさせていただきました。  
そこのサイトに著者のGitHub上のコードが公開されておりました。下記のURLです。  
<https://github.com/atsushijike/AudioService>

そこでこのソースの動作確認をしようとしたのですが、  
AudioServiceクラスのdeinit内にリソース解放のための  
buffer.deallocate()が記述されています。

[code]

    deinit{
        buffer.deallocate()
    }
    
[/code]

しかし、動かしてみると  
error for object 0x11a94f000: pointer being freed was not allocated  
というメッセージが出ました。

試しにbuffer.deallocate()を消してみると通りましたが、bufferが解放されていないのでは無いかと思います。  
しっかりと解放する方法を知りたいのですが、調べてもなかなか見つけることができませんでした。  
解決のためご助言賜りたいです。


CC BY-SA 4.0
2018-10-18T06:26:06.547
49409
2018-10-18T07:38:52.413
25745
question
xcode swift4
deallocate()でエラーが出てバッファを解放できない


コードの中身を見る限り、`NSData`や`NSMutableData`を多用していて、とてもSwift的に綺麗なコードとは言えないですね。著者に届かないところでケチをつけてもしかたないことなんで、これ以上は控えますが。

問題のエラーは解放済みの領域を再度解放(二重解放)しようとした時に表示されるものです。解放し過ぎなんですから、条件によっては`buffer.deallocate()`を削除することで解消できる可能性がありますが、別の条件ではご心配いただいているように領域の解放漏れでメモリリークが生じる可能性があります。

一番問題なのは、この行です。

AudioService.swift: 92行:

[code]

    data = NSData(bytesNoCopy: buffer, length: Int(maxPacketCount * bytesPerPacket))
    
[/code]

`NSData.init(bytesNoCopy:length:)`と言うイニシャライザは`buffer`の所有権を引き継ぐ、つまり、`buffer`の解放は`NSData`の側が行うので、これを一度呼び出したら、`buffer`を割り当てた側がその領域を解放する処理を呼び出してはいけません。

> ##
> [`init(bytesNoCopy:length:)`](https://developer.apple.com/documentation/foundation/nsdata/1409454-init)
>
> ### Declaration
[code]

>     init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int)
>  
[/code]

>
> ### Discussion
>
> The returned object takes ownership of the `bytes` pointer and frees it  
>  on deallocation. Therefore, `bytes` must point to a memory block  
>  allocated with `malloc`.
>
> (拙訳)  
>
> 返された`NSData`オブジェクトが`bytes`ポインターの所有権を引き継ぎ、その`NSData`オブジェクトが廃棄(deallocation)される時にその領域を`free`で解放します。従って、`bytes`は`malloc`で割り当てられたメモリ領域を指すものでないといけません。

元のコードは「`malloc`で割り当てられたメモリ領域を指すものでないといけません」は守っているようですが、前半の事情がわかっていないようです。このイニシャライザを呼んだ後は、自前で同じ領域を解放すると冒頭に書いたような二重解放のエラー(場合によってはうまく検出されず、重大かつデバッグ困難な不具合となります)になります。

* * *

現象の説明が長くなってしまいましたが、先の行を次のように書き換えれば、 **`buffer`についての** 二重解放の問題はなくなります。

[code]

    data = NSData(bytesNoCopy: buffer, length: Int(maxPacketCount * bytesPerPacket), freeWhenDone: false)
    
[/code]

`freeWhenDone:
false`と言う引数を追加することにより、所有権は`NSData`の側に移らず、引き続き`buffer`を割り当てた側がその領域を解放する責任を持つことになります。

* * *

ただ、イニシャライザとデイニシャライザが全然釣り合っていませんね。

[code]

    init(_ obj: Any?) {
        startingPacketCount = 0
        maxPacketCount = (48000 * seconds)
        buffer = UnsafeMutableRawPointer(malloc(Int(maxPacketCount * bytesPerPacket)))
    }
    
    deinit {
        buffer.deallocate()
    }
    
[/code]

`buffer`は`malloc`で割り当てられているのですから、解放は`free`で行うべきです。(
**現在のSwiftの実装では**`deallocate()`は内部で`free`を呼んでいるだけなので、動くことは動くでしょうが。)

また、そもそもSwiftの`malloc`の戻り値型は`UnsafeMutableRawPointer!`型になるので、型変換のために`UnsafeMutableRawPointer(`
`)`で囲んでやる必要もありません。

なんで使いもしていない`obj`なんて引数があるのか、なんてことも解せないんですが、とりあえずそれは置いといて、以下のように描き直すべきでしょう。

[code]

    init(_ obj: Any?) {
        startingPacketCount = 0
        maxPacketCount = (48000 * seconds)
        buffer = malloc(Int(maxPacketCount * bytesPerPacket))
    }
    
    deinit {
        free(buffer)
    }
    
[/code]

ついでに47行、`buffer`は一度割り当てられたら気安く書き換えられては困るので`var`ではなく`let`の方が良いですね。(他にも`let`にした方が良いものはまだありそうですが。)

[code]

    let buffer: UnsafeMutableRawPointer
    
[/code]

* * *

まだまだ、メモリ絡みで何か出てきそうな気もしますが、これ以上は本題から外れすぎるように思うので、深追いはせずに置きます。

上記の修正を行えば、二重解放でのエラーは起こらなくなるはずなのでお試しください。


CC BY-SA 4.0
2018-10-18T07:38:52.413
49411
2018-10-18T07:38:52.413
13972
49409
answer