Posted at

Cのポインタを使う処理をSwiftで書く

More than 3 years have passed since last update.

昔作ったプログラムをSwiftで書き直す過程で、ポインタを使った処理の書き換えが必要になりました。このときに、どのように書いたかをまとめてみました。


構造体のメモリ確保

構造体を使った処理ですが、Cでは「malloc」関数を使ってメモリ確保したり、スタックに変数で確保し、そのポインタを関数に渡したりします。例えば、次のようなコードです。


main.c

#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

struct Rectangle {
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
typedef struct Rectangle Rectangle;

Rectangle * newRect(int32_t x, int32_t y, int32_t width, int32_t height) {
Rectangle *rect = (Rectangle *)malloc(sizeof(Rectangle));
if (rect) {
rect->x = x;
rect->y = y;
rect->width = width;
rect->height = height;
}
return rect;
}

void printRect(Rectangle *rect) {
printf("{%d, %d, %d, %d}\n", rect->x, rect->y, rect->width, rect->height);
}

int main(int argc, const char * argv[]) {
Rectangle *rect = newRect(0, 0, 100, 100);
if (rect) {
printRect(rect);
free(rect);
}
return 0;
}


Swiftでは、Swiftの構造体を定義して、単純にインスタンスを確保するようにしました。関数は構造体そのものに関するものは、構造体のメソッドにして、その他は、関数や他のクラスのメソッドにしました。このCのコードは、構造体のメソッドでも良いと思います。


Rectangle.swift

import Foundation

public struct Rectangle {
var x: Int32
var y: Int32
var width: Int32
var height: Int32

public func printRect() {
print("{\(self.x), \(self.y), \(self.width), \(self.height)}")
}
}



main.swift

import Foundation

let rect = Rectangle(x: 0, y: 0, width: 100, height: 100)
rect.printRect()


但し、バイナリの塊が必要で、そのために構造体を使っているようなケースのときは、そのバイナリの塊を作る必要があると思います。例えば、ファイルフォーマットやプロトコルのデータ、デバイスとの通信で使用するバイナリデータなどです。例えば、上のCで書かれている「Rectangle」構造体のバイナリデータを作るには、次のようにします。


Rectangle.swift

public func createBuffer() -> UnsafeMutablePointer<Void> {

let buf = malloc(sizeof(Int32) * 4)
if buf != nil {
// 32ビットのint変数が単純に4つ並んでいるので、
// Int32のポインタとして扱える
let p = unsafeBitCast(buf, UnsafeMutablePointer<Int32>.self)
p[0] = self.x
p[1] = self.y
p[2] = self.width
p[3] = self.height
}

return buf
}


バッファを作るメソッドとは別に「NSData」に入れて返すメソッドも良いと思います。なお、「createBuffer」メソッドを作らずに、最初から「NSMutableData」でバッファを確保して、内容を埋めるようにするともっと良いとも思います。


Rectangle.swift

public func createData() -> NSData {

let buf = self.createBuffer()
let data = NSData(bytes: buf, length: sizeof(Int32) * 4)
free(buf)

return data
}



インクリメントとデクリメント

Cでポインタを使ってバッファアクセスを行うときに、頻繁に使用するのがインクリメントとデクリメントです。ポインタの加算・減算なので、参照位置を変更することができ、連続したバッファで使用して、順次アクセスができます。例えば、次のようなCのコードです。


main.c

#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

void fillSequentialNumber(void *buf, int32_t len) {
uint8_t *p = (uint8_t *)buf;
uint8_t *end = p + len;

for (; p != end; p++) {
*p = (p - (uint8_t *)buf) % 256;
}
}

void dumpBuf(void *buf, int32_t len) {
uint8_t *p = (uint8_t *)buf;
uint8_t *end = p + len;

for (; p != end; p++) {
printf("%02x ", *p);
}
printf("\n");
}

int main(int argc, const char * argv[]) {
uint8_t buf[16] = {0};
fillSequentialNumber(buf, 16);
dumpBuf(buf, 16);
return 0;
}


Swiftでは、ポインタは「UnsafePointer」や「UnsafeMutablePointer」です。これらの構造体の「advancedBy」メソッドで同様の処理を行うことができます。例えば、次のようなコードです。


buffer.swift

import Foundation

func fillSequentialNumber(buf: UnsafeMutablePointer<Void>, len: Int) {
var p = unsafeBitCast(buf, UnsafeMutablePointer<UInt8>.self)

for i in 0 ..< len {
p[0] = UInt8(i % 256)
p = p.advancedBy(1)
}
}

func dumpBuffer(buf: UnsafePointer<Void>, len: Int) {
var p = unsafeBitCast(buf, UnsafePointer<UInt8>.self)

for _ in 0 ..< len {
print(String(format: "%02x ", p[0]), separator: "", terminator: "")
p = p.advancedBy(1)
}
print("")
}



main.swift

import Foundation

if let data = NSMutableData(length: 16) {
fillSequentialNumber(data.mutableBytes, len: 16)
dumpBuffer(data.bytes, len: 16)
}



ランダムアクセス

Cでバッファにランダムアクセスしたいときは、「uint8_t」などのポインタとして扱って、そのアドレスへのポインタを必要な型にキャストして代入したり、「memcpy」関数などを使って他のバッファからコピーするなどします。例えば、次のようなCのコードです。


c.main.c

#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ThreeDPoint {
int32_t x;
int32_t y;
int32_t z;
};
typedef struct ThreeDPoint ThreeDPoint;

void setInt(void *p, int32_t offset, int32_t value) {
*((int32_t *)(p + offset)) = value;
}

void copyInt(void *p, int32_t offset, int32_t value) {
memcpy((uint8_t *)p + offset, &value, sizeof(int32_t));
}

int main(int argc, const char * argv[]) {
ThreeDPoint pt = {0};
printf("{%d, %d, %d}\n", pt.x, pt.y, pt.z);

setInt(&pt, 4, 10);
copyInt(&pt, 8, 20);
printf("{%d, %d, %d}\n", pt.x, pt.y, pt.z);

return 0;
}


Swiftでも同様の方法が使えます。インクリメントとデクリメントのように「advancedBy」でアドレスを取得して、キャストしたり同じように「memcpy」関数を使うことができます。


buffer.swift

import Foundation

func setInt(buf: UnsafeMutablePointer<Void>, offset: Int, value: Int32) {
let p = unsafeBitCast(buf, UnsafeMutablePointer<UInt8>.self).advancedBy(offset)
let p2 = unsafeBitCast(p, UnsafeMutablePointer<Int32>.self)
p2[0] = value
}

func copyInt(buf: UnsafeMutablePointer<Void>, offset: Int, value: Int32) {
let p = unsafeBitCast(buf, UnsafeMutablePointer<UInt8>.self).advancedBy(offset)
var i = value
memcpy(p, &i, sizeof(Int32))
}

func printIntArrayInBuffer(buf: UnsafePointer<Void>, count: Int) {
let p = unsafeBitCast(buf, UnsafePointer<Int32>.self)
for i in 0 ..< count {
print("\(p[i]) ", separator: "", terminator: "")
}
print("")
}



main.swift

import Foundation

if let data = NSMutableData(length: 12) {
printIntArrayInBuffer(data.bytes, count: 3)
setInt(data.mutableBytes, offset: 4, value: 10)
copyInt(data.mutableBytes, offset: 8, value: 20)
printIntArrayInBuffer(data.bytes, count: 3)
}