電柱日報

日々の由無し事

CrystalでC言語の関数を使う際のポインタ操作

※:この記事は,Crystal Advent Calendar 2018 の7日目です。

最近の高級言語(って今でも言うんですかね?)では,めっきり触ることが少なくなったポインタ操作ですが,Crystal で libc の関数を直接叩いたりバインディングを書いたりすると,ちょくちょくその必要にかられる場面に遭遇します。

Crystalの公式ドキュメントで 安全でない(unsafe)コード のトップに挙げられているように,どうしても必要な時以外は手を出さない方が良いのですが,C言語の関数を使おうと思うとそうも言ってはいられません。

というわけで,Crystalのオブジェクトとそのポインタとの相互変換方法や,ちょっと特殊なポインタ操作についてご紹介します。

ポインタ型

Crystalでは T 型のポインタは Pointer(T) 型と表現します。

この表記もたまに使う分には明確で良いのですが,なんども出てくると一々書くのが億劫です。また,C言語でよく目にするポインタのポインタ,例えば int ** などは,Pointer(Pointer(LibC::Int)) となってさらに面倒臭くなります。

なので,Pointer(T)T* と書けるようになっています。ポインタのポインタも T** とかけてとても楽。

でもって,あるオブジェクトをポインタに変換する場合には pointerof() を使用します。

p a = 1
#=> 1

p typeof(a)
#=> Int32

ptr_a = pointerof(a)

p typeof(ptr_a)
#=> Pointer(Int32)

逆に,ポインタオブジェクトの #value メソッドを使用すると,そのポインタが指し示すオブジェクトを取得することができます。

# 上のコードの続き

p b = ptr_a.value
#=> 1

p typeof(b)
#=> Int32

この時,変数 ab は同じメモリを参照しています。

上記の例にあるプリミティブな数値型のように,自身を破壊的に変更することがない場合はさほど問題になりませんが,Crystalの一般的なオブジェクトで同じような事をすると,オブジェクトの状態が想定外に変化する場合があるため注意が必要です。

c = [0, 1]

ptr_c = pointerof(c)

d = ptr_c.value

c << 2

p d
#=> [0, 1, 2]

あるポインタを別の型のポインタとして扱う

C言語では,まれに(よく?)ある型のポインタとして宣言された変数を別の型のポインタにキャストして使う,といった場面があります。

例えば,LinuxとMac OS XでMACアドレスを取得する方法 で紹介されているMac版サンプルコードの19行目は以下のようになっています。

dl = (struct sockaddr_dl*)ifa->ifa_addr; 

変数 ifa はその上の11行目で,ifaddrs 構造体として宣言されており,その ifa_addr メンバは本来 sockaddr 構造体のポインタであるはずです。しかしここでは,その ifa_addr メンバを sockaddr_dl 構造体のポインタであるものとして変数 dl へ代入しており,以降dlsockaddr_dl 構造体として利用しています。

ifaddrssockaddrsockaddr_dl 構造体をそれぞれ LibC::Ifaddrs 型,LibC::Sockaddr 型,LibC::Sockaddr 型としてバインディングが定義されていた場合に,同様の操作をするには以下のような処理になります。

# ifa : LibC::Ifaddr
# ifa.ifa_addr : LibC::Sockaddr*
dl = ifa.ifa_addr.as(LibC::SockaddrDl*).value
p typeof(dl)
#=> LibC::SockaddrDl