1

我不得不处理我公司中一个非常古老的代码库,它通过 perl 公开了 C++ api。

在代码审查中,我建议有必要对在 c++ 中分配的内存进行垃圾收集。

这是代码的骨架:

char* convert_to_utf8(char *src, int length) {
    .
    .
    .
    length = get_utf8_length(src);
    char *dest = new char[length];
    .
    .
    // No delete
    return dest;
}

Perl xs 定义:

PROTOTYPE: ENABLE

char * _xs_convert_to_utf8(src, length)
    char *src
    int length

CODE:
    RETVAL = convert_to_utf8(src, length)

OUTPUT:
    RETVAL

所以,我有一条评论说,在 c++ 函数中创建的内存不会被 Perl 垃圾收集。并且 2 个 java 开发人员认为它会崩溃,因为 perl 会垃圾收集 c++ 分配的内存。我建议了以下代码。

CLEANUP:
    delete[] RETVAL

我在这里错了吗?

我还运行了这段代码,并向他们展示了内存利用率的增加,无论有没有 CLEANUP 部分。但是,他们要求提供确切的文件来证明这一点,但我找不到。

Perl 客户端:

use ExtUtils::testlib;
use test;

for (my $i=0; $i<100000000;$i++) {
    my $a = test::hello();
}

C++ 代码:

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

#include "ppport.h"
#include <stdio.h>

char* create_mem() {
    char *foo = (char*)malloc(sizeof(char)*150);
    return foo;
}

XS码:

MODULE = test       PACKAGE = test      
    char * hello()
CODE:
    RETVAL = create_mem();
OUTPUT:
    RETVAL
CLEANUP:
    free(RETVAL);
4

2 回答 2

3

恐怕编写(和编写)Perl XS 文档的人可能认为 Perl 无法神奇地检测用其他语言(如 C++)进行的内存分配来明确记录这一点太明显了。perlguts文档页面中有一点说要通过 Perl XS API 使用的所有内存都必须使用 Perl 的宏才能这样做,这可能会帮助您争论。

于 2016-11-04T06:31:15.350 回答
1

当您编写 XS 代码时,您正在编写 C(或有时是 C++)代码。您仍然需要编写适当的 C/C++,其中包括在适当时释放分配的内存。


您希望 XS 创建的粘合函数如下:

void hello() {
    dSP;                       // Declare and init SP, the stack pointer used by mXPUSHs.
    char* mem = create_mem();
    mXPUSHs(newSVpv(mem, 0));  // Create a scalar, mortalize it, and push it on the stack.
    free(mem);                 // Free memory allocated by create_mem().
    XSRETURN(1);
}

newSVpv复制mem而不是占有它,所以上面清楚地表明free(mem)需要 deallocate mem


在 XS 中,你可以这样写

void hello()
CODE:
    {                          // A block is needed since we're declaring vars.
        char* mem = create_mem();
        mXPUSHs(newSVpv(mem, 0));
        free(mem);
        XSRETURN(1);
    }

或者您可以利用 XS 功能,例如RETVALCLEANUP.

SV* hello()
    char* mem;                 // We can get rid of the block by declaring vars here.
CODE:
    mem = create_mem();
    RETVAL = newSVpv(mem, 0);  // Values returned by SV* subs are automatically mortalized.
OUTPUT:
    RETVAL
CLEANUP:                       // Happens after RETVAL has been converted
    free(mem);                 //   and the converted value has been pushed onto the stack.

或者您也可以利用 typemap,它定义了如何将返回的值转换为标量。

char* hello()
CODE:
    RETVAL = create_mem();
OUTPUT:
    RETVAL
CLEANUP:
    free(RETVAL);

这三个都是完全可以接受的。


关于凡人的笔记。

Mortalizing 是一种延迟的引用计数递减。如果您要在返回之前减少由SV创建的创建,它会在返回之前被释放。相反,通过将其销毁,在调用者有机会检查或占有它(通过增加其引用计数)之前,它不会被释放。hellohellohello

于 2016-11-04T17:46:26.173 回答