世卫组织';当外部程序通过R'调用'Rf#u allocXXX'时,管理内存;SCAPI接口?

世卫组织';当外部程序通过R'调用'Rf#u allocXXX'时,管理内存;SCAPI接口?,r,rcpp,R,Rcpp,我正在用Rust编写一个R包,它通过C API接口与R通信 对我来说,一个基本的问题似乎很棘手,那就是内存管理 首先让我简单地解释一下我的Rust程序是如何与R进行通信的 首先,在R端,它使用.Call()调用C动态库。然后,C库通过与C兼容的ABI链接到Rust静态库 R脚本正在做一些简单的工作,比如输入验证,并根据输入决定调用哪个C函数。然后C程序将调用传递给底层的Rust函数,这些函数作为C兼容函数公开 到目前为止,这是非常清楚的,一旦Rust完成计算并需要返回结果,它就会变得很棘手

我正在用Rust编写一个R包,它通过C API接口与R通信

对我来说,一个基本的问题似乎很棘手,那就是内存管理

首先让我简单地解释一下我的Rust程序是如何与R进行通信的


首先,在R端,它使用
.Call()
调用C动态库。然后,C库通过与C兼容的ABI链接到Rust静态库

R脚本正在做一些简单的工作,比如输入验证,并根据输入决定调用哪个C函数。然后C程序将调用传递给底层的Rust函数,这些函数作为C兼容函数公开

到目前为止,这是非常清楚的,一旦Rust完成计算并需要返回结果,它就会变得很棘手


一个选项是直接调用Rust端的
Rf_allocXXX
函数,并在其中存储结果。然后将R对象的原始指针传递回C,然后传递回R

但我不清楚这是否会导致内存泄漏

在我看来,如果Rust调用
Rf_allocXXX
,那么新的R对象(
SEXP
)将在Rust程序的堆内存上创建。将原始指针传出时,锈迹不会破坏对象。但是接下来会发生什么呢

请注意,Rust以这种方式创建的
SEXP
直接传递回C和R。没有重新分配。所以我似乎很不清楚这个
SEXP
是否会被R的GC正确释放


相关问题,

我从
Rcpp
的源代码中注意到的是,它似乎只是调用R的C API来创建它的各种向量,这些向量是
SEXP
的包装

但我还不清楚它是如何处理内存管理的。它是否只是简单地将
SEXP
对象转回,R就能正确地处理GC?

一切(一如既往)都在(而且总是不容易找到……)

简而言之,当您调用R扩展包时,您基本上需要调用R的API及其
Calloc()
Free()
例程(和变体)。为什么?因为返回到R的任何对象都将成为R对象,并且与所有其他R对象都无法区分,并且行为相同。非常重要的是,当涉及到垃圾收集时

唯一的方法是通过R自己的分配器。所以Rcpp使用它。并创建事实上无法区分的对象。这让一切都起作用了

R> Rcpp::cppFunction("IntegerVector foo() { 
+                           IntegerVector v = {1, 2, 3}; return v; }") 
R> foo()
[1] 1 2 3 
R> identical(foo(), c(1L, 2L, 3L))  
[1] TRUE  
R> identical(foo(), 1:3)    
[1] TRUE    
R> 

但作为第一步,您可以在Rust中完成计算结果的工作,然后支付一次性转换成本(从对象到
SEXP
R期望的
.Call()
)。“先走后跑”等等。铁锈装订会很酷。我想你知道Jeroen和其他人已经做了一些工作吗?

更新此问题:

在阅读了《编写R扩展》一节之后,现在我意识到

  • 如果一个可能使用其他语言的外国程序创建了一个
    SEXP
    对象通过
    R\u allocXXX
    接口或
    Rcpp
    中的等效项,然后 负责释放内存,
    • 在调用
      .C()
      .call()
      .External()
    • 或者当出现错误时
  • 但是如果R对象是通过
    malloc
    或类似方式手动创建的 接口,用户负责释放内存,无论 此对象是在C、
    Rcpp
    或其他外部程序中创建的
因此,如果我们假设R在释放内存之前没有死机,那么在大多数情况下 我们应该使用
R\u allocXXX
绑定来创建
SEXP
,然后 将无内存泄漏。一旦物体转到R,它就安全了

但在处理一般的外部功能接口(FFI)时,仍需谨慎

  • 如果我们手动分配内存并将一个C对象返回给C接口,那么 在转换C对象之前,我们需要确保FFI的任何一方都不会恐慌 至
    SEXP
    。否则不会释放内存
  • 同样,这也适用于当我们在C程序中手动分配内存时, 并将对象发送到外部程序
  • 在任何情况下,手动分配的内存都需要手动释放

因此,一般来说,只要有可能,我们就应该使用
R\u allocXXX
接口来保护内存安全。

是的,我找到了Jeroen的演示和。我当前的实现只是计算中间结果,然后将其作为通用C结构或基元类型发送给C包装函数,然后让C将这些C类型重新打包到
SEXP
。但如果我能在Rust中直接创建
SEXP
就更好了。我担心内存泄漏,因为一般来说,建议分配内存的人负责释放内存。例如,如果我在Rust中创建了一个向量,并将其指针和长度发送到C,那么通常我需要通过回调函数销毁该向量。但这一点并不清楚,因为R有自己的垃圾收集器。还有内存分析,请参阅(再次)编写R扩展。此外,CRAN非常勤奋,在所有软件包、SAN/UBSAN、analysis等方面运行valgrind。“不管人们怎么想”(基于第一眼),这不是一堆胡闹的业余代码。很可靠,对不起,我没说到你问题的那一部分。“当然”——在C++(有或没有RCPP)中,我们使用STL一整天,它有它的分配器。现代语言可以做到这一点:一旦超出范围,一切都会好起来。您不能将这些“本机”分配器用于返回到R的内容。通常情况下,值在超出范围时会被删除。实际上,如果一个值而不是它的引用被传递给另一个函数,