PerlXS: sv_2mortal()やhv_store()のこと

そろそろXSをちゃんとやろうと思ったので、メモリ周辺のことが良くわからなくて放置していたsv_2mortal()やhv_store()のことについて調べた。

参考: perlguts
http://www.kt.rim.or.jp/~kbk/perl5.005/perlguts.html


最初に結論を書いてしまうと

[1: sv_2mortal()]
- returnしない変数は常にsv_2mortal()を通す
  (揮発性をもたせる)
- SVはreturnするときはsv_2mortal()を通してはダメ
- AV, HVはreturnするときもsv_2mortal()を通す

[2: newRV_inc()]
- リファレンス変数(RV)を作るときはnewRV_inc()を
  使い参照カウントをインクリメントしておく
  (newRV_noinc()はダメ)
- リファレンス変数もsv_2mortal()を通す

[3: hv_store()]
- hv_storeの引数は事前にSvREFCNT_inc()で
  インクリメントしておく
- hv_storeがNULLを返した場合はストアに失敗。
  SvREFCNT_dec()でデクリメントしておく

順番に説明。まず変数の型はSV、AV、HVがあり、それぞれスカラ、配列、連想配列(ハッシュ)を指す。
これらの変数は引数として与えられていない場合は、コード中でnewSV()などを使ってオブジェクトを作る。この際にオブジェクトに揮発性という性質を与えていないとスコープを抜けた後もメモリが開放されずリークする。そこで

void
func()
CODE:
  SV *sv = sv_2mortal(newSV(100));

などとしてsv_2mortal()関数を使って揮発性を与える。こうすることで参照カウントがなくなったらオブジェクトが破棄される。
コード中で作ったオブジェクトを戻り値として返したい場合、SV型だけは揮発性を与えてはいけない。つまり、以下のようにnewSV()だけを使う。

SV *
func()
CODE:
  SV *sv = newSV(100);
  RETVAL = sv;
OUTPUT:
  RETVAL

何故かは不明。多分Perl側でメモリを開放してくれてるんだろうけどAVやHVでは逆に揮発性を与えておかないといけなかったりして謎。

次に、リファレンス変数。これは既にあるオブジェクトに対する参照をつくる。これにはnewRV_inc()を使う。

void
func()
CODE:
  SV *sv = sv_2mortal(newSV(100));
  SV *rv = sv_2mortal(newRV_inc(sv));

リファレンス変数と言っても実際はSV型なのでSVのように扱ってOK。従ってちゃんと揮発性も与えないとダメ。似たようなのでnewRV_noinc()というのがあるが、こちらは引数の参照カウントを足してくれないので、まだ参照が残ってるのにオブジェクトが開放されてしまったりして大変な事になる。

最後にHV型に要素を突っ込む方法。これにはhv_store()を使う。hv_store()は突っ込む要素の参照カウントをインクリメントしてくれないので事前に自分でSvREFCNT_inc()を使って参照カウントを増やさないといけない。具体的には以下の通り

void
func()
CODE:
  SV *sv = sv_2mortal(newSV(100));
  HV *hv = (HV *)sv_2mortal((SV *)newHV());
  SV **ret = NULL;
  char *key = "test";

  SvREFCNT_inc(sv);
  ret = hv_store(hv, key, strlen(key), sv, 0);
  if (ret == NULL) {
    SvREFCNT_dec(sv);
  }

hv_store()が失敗した場合はインクリメントした分をSvREFCNT_dec()でデクリメントしないといけないので注意。

以上。多少はXSプログラミングの参考になれば幸い。

↓PerlXSを学ぶにはこの本!