SwiftでC言語互換のポインタ操作が面倒

というかそんなことをしなければ良いのかも知れませんが画素などを直接操作する場合には必要になってしまうこともないわけではないのでいろいろとやってみました。ちなみに、普通であればそんな操作をする場合であればC言語やObjective-Cにコードを持って行ってそこで処理した結果をSwiftに拾い上げた方が良いと思います。まあ、それは置いておいて、どんなコートが互換コードとなるのか見てみるとその性質が見えてくるのではないでしょうか。

 

SwiftでC言語互換のポインタを扱う

まずはこれから。この辺はまだSwiftの言語仕様などを解説している本やページを見れば書いてあると思いますがちょっとだけ解説。関数ポインタやクラスのポインタでは扱いが微妙に異なりますが通常こんなものが必要なのは生のデータを扱う時だと思いますのでそれを念頭に。

まずポインタの種類から。Swiftではポインタを扱うことができませんから何らかの方法でポインタを保持する必要があります。そのためにとある型が用意されています。それがUnsafePointer<T>、UnsafeMutablePointer<T>、AutoreleasingUnsafeMutablePointer<T>の三つになります。それぞれ、以下のような役割となります。

C言語/Objective-Cの型 Swiftの型
const T * UnsafePointer<T>
T * UnsafeMutablePointer<T>
T ** AutoreleasingUnsafeMutablePointer<T>

Tの部分は使用する型に置き換えて下さい。ちなみに、C言語/Objective-Cではvoidと書くvoid型ですが、SwiftではVoidとVが大文字となり、NULL(nil)ポインタはSwiftではすべてnilで扱われます。また、クラスオブジェクトを扱わないのであれば上の2つだけでデータ操作は何とかなると思います。AutoreleaseingUnsafeMutablePointerはどちらかというとObjective-Cのオブジェクトを保持しておくためにあるのでC言語レベルの処理では使わないはずです。

 

ポインタのキャスト

ポインタなのでキャストできないとどうしようもありません。が、Swift上では独立した型なのでそのままコピーは一切できません。C言語と同じようにキャスト構文を書く必要があります。たとえば、C言語上で

unsigned char *buf;
int *array;
・・・
array = (int *)buf

と書きたい場面があるとします。このとき、型キャストを書くと

var buf: UnsafeMutablePointer<UInt8>
var array: UnsafeMutablePointer<Int32>
・・・
array = UnsafeMutablePointer<Int32>(buf)

となります。このあたりはまだ理解しやすいのではないかと思います。微妙に見えるのは型キャストのたびに変換する型のオブジェクトを擬似的に作成するところでしょうか。ちなみに、unsigned charは8bitの符号無し整数と考えてUInt8に、intは32bit符号あり整数と考えてInt32と型を限定してつけているのにも注意です。変数のサイズも考えて書かないと互換とは言い切れなくなるので。

 

UnsafePointerやUnsafeMutablePointerのポインタを参照する

ポインタを参照する時の手順です。単純にポインタとしてアクセスする場合もありますし、ポインタを配列のように扱ってインデックスをつけて処理する場合もあります。この場合はどうするのかというと・・・。いろいろとパターンがあるのでいくつかのパターンで見ていきます。基本的にはUnsafePointer<T>やUnsafeMutablePointer<T>が持つプロパティであるmemoryを通します。が、このmemoryの使い勝手が微妙に面倒。特に構造体で各メンバ単体での取得ができなかったり、インデックス参照ができなかったり、と。

で、例ですが、一応構造体の例もあるので構造体を一つ宣言してそれについてのアクセスも見ていきます。意味が無い処理も多少ありますがサンプルと言うことで。

typedef struct clr32{ unsigned char b,g,r,a; } CLR32;

const CLR32 *p1; const int *p2; CLR32 *p3; int *p4; unsigned char gray;
・・・
// 以下p1,p2,p3,p4には有効なアドレスが入っているものとする(メモリ領域は十分にあるものとする)
// 読み込み+計算処理
gray = (unsinged char)((int)p1->r * 306 + (int)p1->g * 601 + (int)p1->b * 116) >> 10);

// 書き込み処理
p3->b = p3->g = p3->r = gray; p3->a = p1->a; *p4 = *p2;

// 読み込み+書き込み(インデックス、ポインタ演算)
p3[1] = *(p1 + 1); p4[1] = *(p2 + 1);

という処理があったとします。なんかグレースケール変換の処理が混じっていますがまあ気にせず。これをSwiftで再現すると・・・

internal struct CLR32{
    var b: UInt8 = 0
    var g: UInt8 = 0
    var r: UInt8 = 0
    var a: UInt8 = 0
}
var p1: UnsafePointer<CLR32>
var p2: UnsafePointer<Int32>
var p3: UnsafeMutablePointer<CLR32>
var p4: UnsafeMutablePointer<Int32>

・・・
// 以下p1,p2,p3,p4には有効なアドレスが入っているものとする(メモリ領域は十分にあるものとする)

// 読み込み+計算処理
let p1mem = p1.memory
let gray = UInt8((Int(p1mem.r) * 306 + Int(p1mem.g) * 601 + Int(p1mem.b) * 116) >> 10)
let clr = CLR32(b: gray, g: gray, r: gray, a: p1mem.a)

// 書き込み処理
p3.memory = clr
p4.memory = p2.memory

// 読み込み+書き込み(インデックス、ポインタ演算)
// 書き方はポインタ演算の書き方のみ可能
p3.advancedBy(1).memory = p1.advancedBy(1).memory
p4.advancedBy(1).memory = p2.advancedBy(1).memory

どうですか。面倒になったでしょう。memoryを通して扱うのですが、特に構造体などで注意ですが構造体のプロパティ(C言語などではメンバ)を通すと書き込み特性が失われてしまうので読み込む場合はそのまま参照してもどうと言うことはないのですが、構造体のデータを書き込むときは書き込む前にデータを集めて一気に書き込まないとうまくいきません。また、インデックス付きの場合はadvancedByでインデックス移動したポインタオブジェクトを新たに作成してその場所のmemoryを参照する、というやり方が必要になります。

 

UnsafePointerやUnsafeMutablePointerのポインタ位置を動かす

C言語などではインクリメントなどで次の要素を簡単に指せるのですが、Swift上で「再現する」となると意外と面倒です。一つにはインデックスを別変数にして参照する、という方法があり、これは上に紹介したやり方が使えますね。では本当に再現するとどんなコードになるのか、というと。まずC言語上で

int *p;

//ポインタのインクリメント
p++;

//ポインタを二つ動かす
p += 2

となるコードですが、(ここまでの説明を見た人なら何となく分かると思いますが)これをSwiftで書くと

var p: UnsafeMutablePointer<Int32>

//ポインタのインクリメント
p = p.advancedBy(1)

//ポインタを二つ動かす
p = p.advancedBy(2)

とadvancedByを用いた形式に早変わりです。移動量はオブジェクトの分だけ動くのでsizeof等の考え方は必要ないのがありがたいですか。関数の戻り値を代入しているように見えますが、一枚後ろではオブジェクトを生成してそれを保持するようにするコードなのでポインタを動かすたびにオブジェクトを生成しているように見えて嫌な感じもする人もいるかも。

 

UnsafeMutablePointerでのメモリの確保および解放

もちろん「C言語互換のポインタ」と言っているだけあってメモリの確保および解放は普通にできます。UnsafePointerはconstポインタなのでメモリの確保や解放とは無縁でしょうから無視して。C言語でメモリを確保する場合はだいたい

int *array;

//メモリの確保
array = (int *)calloc(100,sizeof(int)); //もしくはmalloc(100 * sizeof(int))
・・・
//メモリの解放
free(array);

と書きますが、これをSwiftに持って行くと

var array: UnsafeMutablePointer<Int32>

//メモリの確保
array = UnsafeMutablePointer<Int32>.alloc(100)
・・・
//メモリの解放
array.dealloc(100);

となります。メモリの解放時に解放するデータサイズを指定する必要があるのでメモリを確保した本体は同じポインタを保持した方がわかりやすいと思います。

で、私がとあることを忘れてテスト段階でアプリを落としまくった要注意点が一つ。

UnsafeMutablePointerはC言語のポインタをSwiftで扱うためのものなので(UnsafeMutablePointer自身はARCの管理下にあるが)UnsafeMutablePointer内部のポインタにARCなんて便利な構造はなく、メモリ管理はプログラマが行わなければならない。つまり確保したメモリはUnsafeMutablePointerがオブジェクトとして解放されるタイミングになってもdeallocは呼ばれないので自分で呼び出さないとメモリリークを起こすことに注意しよう

はじめはUnsafeMutablePointerのスコープが切れた段階で自動解放されるものだと思っていたので対応するコードを書かなかったのですが・・・。書かないとだめですので解放のポイントを間違えないようにしましょう。

 

無理矢理書かない方がいいのかも

と思わないでもない今日この頃。無理に書くくらいならC言語で書いたコードを連結した方がはるかにわかりやすいですね。メモリの確保と解放だけをSwift上で行うことになるはずなのでその場合はメモリの管理を忘れずに。

なお、本来ならSwiftのような言語では変数宣言は実際にその変数を使うまで遅らせて宣言と同時に初期化するのが正しい作法です。が、今回はC言語との対比を見るためにそのような書き方をせずに書いていますのであしからず。


SwiftでC言語互換のポインタ操作が面倒」への1件のフィードバック

  1. ピンバック: お気に入り – ブックマーク

コメントを残す

メールアドレスが公開されることはありません。

*

この記事のトラックバック用URL