Передавая ссылку на массив в обратный вызов Perl с помощью call_sv (), я должен использовать newRV () или newRV_noinc ()? - PullRequest
0 голосов
/ 08 октября 2019

У меня есть этот код XS (XsTest.xs):

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

MODULE = My::XsTest  PACKAGE = My::XsTest
PROTOTYPES: DISABLE

void
foo( callback )
       SV *callback
    PREINIT:
        AV *array;
        SSize_t array_len;
        SV **sv_ptr;
        SV *sv;
        double value;
    CODE:
        if ( !SvROK(callback) ) {
            croak("Not a reference!");
        }
        if ( SvTYPE(SvRV(callback)) != SVt_PVCV ) {
            croak("Not a code reference!");
        }
        /* This array will go out of scope (and be freed) at the end of this XSUB 
         * due to the sv_2mortal()
         */
        array = (AV *)sv_2mortal((SV *)newAV()); /* Line #28 */
        /* NOTE: calling dSP is not necessary for an XSUB, since it has 
         * already been arranged for by xsubpp by calling dXSARGS 
         */
        printf( "Line #28: SvREFCNT(array) = %d\n", SvREFCNT(array));
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        EXTEND(SP, 1);
        /* Should I use newRV_inc() or newRV_noinc() here? Or does it not 
         * matter?
         * NOTE: XPUSHs mortalizes the RV (so we do not need to call sv_2mortal()
         */
        XPUSHs((SV *)newRV_inc((SV *) array)); /* Line #41 */
        printf( "Line #41: SvREFCNT(array) = %d\n", SvREFCNT(array));
        PUTBACK;
        call_sv(callback, G_VOID);
        printf( "Line #45: SvREFCNT(array) = %d\n", SvREFCNT(array)); /* Line #45: */
        array_len = av_top_index(array) + 1;
        printf( "Array length: %ld\n", array_len );
        if ( array_len != 1 ) {
            croak( "Unexpected array size: %ld", array_len );
        }
        sv_ptr = av_fetch( array, 0, 0 );
        sv = *sv_ptr;  
        if (SvTYPE(sv) >= SVt_PVAV) {
            croak("Not a scalar value!");
        } 
        value = SvNV(sv);
        printf( "Returned value: %g\n", value);
        FREETMPS;  /* Line # 58 */
        LEAVE;
        printf( "Line #60: SvREFCNT(array) = %d\n", SvREFCNT(array));

Я пытаюсь выяснить, использовать ли newRV_inc() или newRV_noinc()в строке # 41.

Обратный вызов Perl определен в тестовом скрипте p.pl:

use strict;
use warnings;
use ExtUtils::testlib;
use My::XsTest;

sub callback {
    my ( $ar ) = @_;
    $ar->[0] = 3.12;
}
My::XsTest::foo( \&callback );

Выходные данные при запуске p.pl:

Line #28: SvREFCNT(array) = 1
Line #41: SvREFCNT(array) = 2
Line #45: SvREFCNT(array) = 2
Array length: 1
Returned value: 3.12
Line #60: SvREFCNT(array) = 2

Насколько я вижу, если я использую newRV_inc():

  • Счетчик ссылок array устанавливается на 1 в строке № 28 при вызове newAV(),
  • затем оно уменьшается до нуля также в строке # 28 при вызове sv_2mortal() в том же массиве,
  • в строке # 41 Я создаю ссылку, используя newRV_inc(), и счетчик ссылок array увеличиваетсяобратно от 0 до 1 (из-за _inc в newRV_inc()),
  • в строке # 58, вызывается макрос FREETMPS, но это не влияет (?) refcount array, поскольку он был создан за пределами границы SAVETMPS, которую мы установили для временных колбэков. С другой стороны, ссылка, которую мы выдвинули в строке # 41, здесь освобождается (поскольку она была сделана смертной), что заставляет ее отказаться от владения array, и, следовательно, счетчик ссылок array будет снова уменьшен до нуля.
  • в строке # 60, выход XSUB и array будут освобождены (при следующем вызове цикла perop runop в FREETMPS), так как он имеет счетчик ссылок, равный нулю. Все скаляры в массиве также будут освобождены в этой точке (?).

Проблема с приведенными выше рассуждениями состоит в том, что они не согласуются с выводом из SvREFCNT(), как показано выше. Согласно выводу, счетчик ссылок array равен 2 (а не 1) на выходе.

Что здесь происходит?

Дополнительные файлы для воспроизведения:

lib / My / XsTest.pm :

package My::XsTest;
use strict;
use warnings;
use Exporter qw(import);
our %EXPORT_TAGS = ( 'all' => [ qw(    ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(    );
our $VERSION = 0.01;
require XSLoader;

XSLoader::load();
1;

Makefile.PL :

use 5.028001;
use strict;
use warnings;
use utf8;
use ExtUtils::MakeMaker 7.12; # for XSMULTI option

WriteMakefile(
  NAME          => 'My::XsTest',
  VERSION_FROM  => 'lib/My/XsTest.pm',
  PREREQ_PM     => { 'ExtUtils::MakeMaker' => '7.12' },
  ABSTRACT_FROM => 'lib/My/XsTest.pm',
  AUTHOR        => 'Håkon Hægland <hakon.hagland@gmail.com>',
  OPTIMIZE      => '',  # e.g., -O3 (for optimize), -g (for debugging)
  XSMULTI       => 0,
  LICENSE       => 'perl',
  LIBS          => [''], # e.g., '-lm'
  DEFINE        => '', # e.g., '-DHAVE_SOMETHING'
  INC           => '-I.', # e.g., '-I. -I/usr/include/other'
)

Компиляция

Чтобы скомпилировать модуль, запустите:

perl Makefile.PL
make

1 Ответ

1 голос
/ 08 октября 2019

Вы должны использовать newRV_inc ().

Ваша настоящая проблема в том, что вы создаете новый RV, который протекает. Тот факт, что RV никогда не освобождается, означает, что счетчик ссылок на массив никогда не уменьшается. Вам необходимо обнулить возвращаемое значение newRV_inc ().

Еще один комментарий: счетчик ссылок массива не уменьшается до нуля, когда вы обнуляете его;это остается как 1. Я не уверен, откуда у вас эта идея. На самом деле происходит то, что когда вы вызываете newAV (), вы получаете AV со счетчиком ссылок, равным 1, что слишком высоко. Оставлено как есть, будет течь. sv_2mortal () не меняет счетчик ссылок массива, но он получает право владения одной ссылкой, которая «исправляет» общее количество ссылок, и массив больше не будет утекать.

...