在祝福XS代码中的空引用时泄漏

cre*_*ive 3 perl garbage-collection memory-leaks reference xs

我试图做一个相当于XS的XS:

package RefTestPP;
use strict;
use warnings;

sub new {
    my ($class, $self) = (@_, {});
    return bless $self, $class;
}

1;
Run Code Online (Sandbox Code Playgroud)

这种构造函数应该在被调用时"自动"它的基础RefTestPP->new(),或者使用给定的引用作为基础,如RefTestPP->new({ stuff => 123 });.

但是,我遇到了无法解释的泄漏.这是我的RefTest.xs档案:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

MODULE = RefTest PACKAGE = RefTest

PROTOTYPES: ENABLE

void
new(sclass="RefTest", base=sv_2mortal(newRV_noinc((SV *) newHV())))
    const char *sclass
    SV *base
    PREINIT:
        HV *stash;
    PPCODE:
        stash = gv_stashpv(sclass, 0);
        ST(0) = sv_bless(base, stash);

        XSRETURN(1);

void
new_leaky(sclass="RefTest", base=newRV_noinc(sv_2mortal((SV *) newHV())))
    const char *sclass
    SV *base
    PREINIT:
        HV *stash;
    PPCODE:
        stash = gv_stashpv(sclass, 0);
        ST(0) = sv_bless(base, stash);

        XSRETURN(1);
Run Code Online (Sandbox Code Playgroud)

以及RefTest.t检测泄漏的文件:

use strict;
use warnings;

use Devel::Leak;
use Test::More;
BEGIN { use_ok('RefTest') };

sub test_leak (&$;$) {
    my ($code, $descr, $maxleak) = (@_, 0);
    my $n1 = Devel::Leak::NoteSV(my $handle);
    $code->() for 1 .. 1000;
    my $n2 = Devel::Leak::CheckSV($handle);
    cmp_ok($n1 + $maxleak, '>=', $n2, $descr);
}

# OK
test_leak { my $ref = RefTest->new() or die }
    'first sv_2mortal(); then newRV_noinc()', 2;

# also OK
test_leak { my $ref = RefTest->new_leaky({}) or die }
    'first sv_2mortal(); then newRV_noinc(); pre-init base', 2;

# leaks!
test_leak { my $ref = RefTest->new_leaky() or die }
    'first newRV_noinc(); then sv_2mortal()', 2;

done_testing 4;
Run Code Online (Sandbox Code Playgroud)

(正确编译所需的其余文件是生成的默认文件h2xs -A -n RefTest)

重点是:

  1. 作为参数传递给构造函数的基本引用永远不会泄漏;
  2. XS代码中创建的base sv_2mortal(newRV_noinc((SV *) newHV()))也不泄漏;
  3. newRV_noinc(sv_2mortal((SV *) newHV()))漏洞创建的base (最终Attempt to free unreferenced scalar: SV 0xdeadbeef在全局破坏期间导致臭名昭着的消息).

有什么理由sv_2mortal(newRV_noinc(...))可以与众不同newRV_noinc(sv_2mortal(...))吗?我只是做错了吗?

ike*_*ami 5

sv_2mortal是一个延迟的refcount减量.(它发生在调用者有机会参考它或复制它之后.)在这篇文章中,我将给出引用计数,好像sv_2mortal立即减少,但将使用星号("*")来表示这一点.


以下代码使引用变为临界:

sv_2mortal(newRV_noinc((SV*)newHV()))
Run Code Online (Sandbox Code Playgroud)

所以,

  1. 哈希值为REFCNT = 1.这很好,因为您创建的引用包含对它的引用.
  2. 引用具有REFCNT = 0*.这很好,因为没有引用它.

以下代码使哈希值变为临界值:

newRV_noinc(sv_2mortal((SV*)newHV()))
Run Code Online (Sandbox Code Playgroud)

所以,

  1. 哈希值为REFCNT = 0*.这很糟糕,因为您创建的引用包含对它的引用.这将导致过早的释放(这就是为什么你在获得引用时得到"尝试释放未引用的标量").
  2. 引用具有REFCNT = 1.这很糟糕,因为没有引用它.这是一个泄漏.