XSでメモリークを避けるたった一つの方法

XSでメモリリークを避ける基本原則は、それほど難しくない。すなわち、作ったSV(およびSVファミリ)はすぐsv_2mortal()するのである。mortalなSVはスコープ*1から抜けるときに解放されるため、メモリリークは起こらない。つまり、あるスコープ内で新しく作ったすべてのSVをmortalな状態にしておくということだ。
この原則のもとでコードを書くと、誤ってリファレンスカウントを増やさなかったケースでは警告が頻繁に起きる。しかし、少なくともメモリリークは起こらない*2メモリリークの検出は難しいので、警告が出るのは福音であろう。
もちろんこれは原則で、メモリリークにまつわることで覚えなければならないことは決して少なくない。
たとえば、XSUBの戻り値をSV*にするとき、sv_2mortal(RETVAL)してはいけない。これはPerlの仕様ではなくxsubppが勝手にsv_2mortal(RETVAL)してしまうからである。これでは基本原則を守ることが難しいため、これは設計ミスといってよい。私はこれを避けるため、戻り値をSV*にしたときはOUTPUT: RETVALを指定せず、ST(0) = RETVAL; XSRETURN(1)を直接使用することが多い*3

SV*
foo(...)
CODE:
{
    /* 作成したSVは常にmortalにする原則に基づき、
       SVs_TEMPを指定してnewする */
    RETVAL = newSVpvs_flags("foo", SVs_TEMP);
    /* OUTPUT: RETVALをするときは、
       ここでSvREFCNT_inc(RETVAL)が必要 */
    ST(0) = RETVAL;
    XSRETURN(1);
}

また、このルールでハッシュリファレンスなどを作るときは以下のようになるが、煩雑である。

    HV* const hv    = (HV*)sv_2mortal((SV*)newHV());
    /* この場合はnewRV_inc() */
    SV* const hvref = sv_2mortal(newRV_inc((SV*)hv));

したがって、このような状況に限り、以下のように内側のsv_2mortal()を省略することにしている。

    HV* const hv    = newHV();
    /* この場合はnewRV_noinc() */
    SV* const hvref = sv_2mortal(newRV_noinc((SV*)hv));

このような例外が煩雑であることは否めないが、この原則でメモリリークは減らせるはずだ。

*1:ENTER; SAVETMPS; ... FREETMPS; LEAVE;のこと。XSUBは基本的にスコープに囲まれている。

*2:あるいは、非常に起きにくい

*3:バッドノウハウなので必ずしも奨励はしない