为什么“obstr”包中的“obj_addr”返回地址与指针地址不同?

avwztpqn  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(121)

我写了一个非常简单的R包,它返回R中整数值的指针:
R代码 Package 器:

getAddrss = function(expandMat)
{
   result <- .C("pointer2", address = expandMat)
}

C++代码:

#include <cstdlib>
#include <R.h>
#include <string>
#include<iostream>

extern "C"
{

void pointer2(int* address)
{

    std::cout<< "Memory Address: " << address <<std::endl;
    std::cout<< "Value: " << address[0] <<std::endl;
}
}

但是,上述函数返回的内存地址与“lobstr”包中的“obj_addr”不同。
范例:

library(lobstr)
library(myRPackage)
x=3:5
> obj_addr(x)
[1] "0x7f0e416562a8"
> getAddrss(x)
Memory Address: 0x7f0e41655c98
Value: 3
b0zn9rqh

b0zn9rqh1#

您在这里使用int*。然而,R integer向量不是C int(即使是长度为1的向量)。如R Internals中所述:
R用户认为变量或对象是绑定到值的符号。这个值可以被认为是一个SEXP(一个指针),或者是它所指向的结构,一个SEXPREC.[这是]一个C结构,包含如上所述的64位头部[和]三个指针(指向属性,前一个和下一个节点)。
你可以在lobstr包中看到这一点:

x  <- 1L
lobstr::sxp(x)
# [1:0x7fffda8ee400] <INTSXP[1]> (named:5)

INTSXP是24种可能的类型之一。参见R Internals了解其他SEXP类型和包含的字段。
要获取指向R对象的指针,而不是int,您需要接受SEXP参数。这正是lobstr所做的。obj_addr_()(最终调用的函数)的C++源代码是:

inline std::string obj_addr_(SEXP x) {
  std::stringstream ss;
  ss << static_cast<void *>(x);
  return ss.str();
}

当这个函数被另一个C函数调用时,它可以返回ss.str(),这是一个Cstd::string对象。然而,在我们的例子中,我们希望将内存地址作为字符向量返回给R:
R字符向量存储为STRSXP s,一种类似VECSXP的向量类型,其中每个元素都是CHARSXP类型。您可以通过调用mkChar并提供一个以null结尾的C样式字符串来获得CHARSXP
std::string创建R字符向量可以(通常应该)使用Rcpp::StringVector非常简单地完成。然而,由于这抽象了创建SEXP的一些值得展示的细节,我将在这里避免它,而是将我们的std::string转换为char数组并使用mkChar()
我们可以使用inline R包来编译一些代码,它返回内存地址的一个元素字符向量:

get_pointer  <- inline::cfunction(sig = c(x = "integer"),
    body = '
    // Get the pointer to the SEXP like lobstr
    std::stringstream ss;
    ss << static_cast<void *>(x);

    // Convert the std::string to char array
    std::string addr_str = ss.str();
    char* addr_chr = new char[addr_str.length() + 1];
    strcpy(addr_chr, addr_str.c_str());
    
    // Create a character vector of length 1
    SEXP addr = PROTECT(allocVector(STRSXP, 1));
    // Cast addr_chr to CHARSXP as set it to the first element
    SET_STRING_ELT(addr, 0, mkChar(addr_chr));
    // For the garbage collector - see Advanced R
    UNPROTECT(1);    
    return addr;
    ',
    includes = "#include <sstream>"
)

此函数返回与lobstr::obj_addr()相同的值:

x  <- 1L
lobstr::obj_addr(x) # "0x7fffc74eb368"
get_pointer(x) # "0x7fffc74eb368"

x  <- 1:10
all(sapply(x, \(x) identical(lobstr::obj_addr(x), get_pointer(x)))) # TRUE

R的C接口阅读资源

我发现这些很有用:

  1. R Internals -R内部结构指南和R核心团队的编码标准。
    1.威克姆的R Internals Github repo的vectors章节。
  2. R's C Interface一章,Hadley威克姆著。
    在最后一个链接中,威克姆说:
    我不建议使用C编写新的高性能代码。用Rcpp写C++。Rcpp API保护您免受R API的许多历史特性的影响,为您负责内存管理,并提供许多有用的帮助方法。
    这是在2014年写的。有趣的是,在2020年,dplyr v1.0.0发布了,更新日志指出:
    dplyr删除了两个最重的依赖项:RcppBH。这将使从源代码构建变得更加容易和快速。
    dplyr开发者似乎已经接受了优化构建时间可能会有轻微的性能损失。这是here讨论。最终,我认为,了解R对象如何存在于C中是很重要的,但我的印象是,绝大多数包开发人员更喜欢Rcpp的抽象,而不是直接处理R的C接口。

相关问题