如果在Rcpp中修改IntegerVector的值:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
void test(IntegerVector x) {
x[5] = 77;
}
在R中运行test()
函数后:
x <- 10:1
test(x)
print(x) # 10 9 8 7 6 77 4 3 2 1
sum(x) # 55
sum函数返回原始数组10:1
的值。
我怎么解决这个问题?
使用例如改为x <- sample(10L)
。
答案 0 :(得分:8)
@F.Privé的怀疑是正确的。这是ALTREP的问题,c.f.尚不支持Rcpp。 Rcpp/#812和Rcpp/#906。我们可以通过检查变量String html = "<pre class=\"cg-msgbody cg-view-msgbody\">"
+ "<span class=\"cg-msgspan\">"
+ "<span>**the text I want to get is present here, "
+ "how can I get it using JSoup?**</span>"
+ "</span>"
+ "</pre>";
org.jsoup.nodes.Document document = Jsoup.parse(html);
//a with href
Element link = document.select("span").last();
System.out.println("Text: " + link.text());
来更明确地看到这一点:
x
第一块给出:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
void test(IntegerVector x) {
x[5] = 77;
}
/*** R
x <- 10:1
.Internal(inspect(x))
test(x)
.Internal(inspect(x))
print(x) # 10 9 8 7 6 77 4 3 2 1
sum(x) # 55
x <- 10:1
.Internal(inspect(x))
x[6] <- 77L
.Internal(inspect(x))
print(x) # 10 9 8 7 6 77 4 3 2 1
sum(x)
*/
第二个块给出:
> x <- 10:1
> .Internal(inspect(x))
@55f79a9d6c58 13 INTSXP g0c0 [NAM(3)] 10 : 1 (compact)
> test(x)
> .Internal(inspect(x))
@55f79a9d6c58 13 INTSXP g0c0 [NAM(3)] 10 : 1 (expanded)
> print(x) # 10 9 8 7 6 77 4 3 2 1
[1] 10 9 8 7 6 77 4 3 2 1
> sum(x) # 55
[1] 55
因此,在更改向量中的值之后,它仍然声称是> x <- 10:1
> .Internal(inspect(x))
@55f79b1f9018 13 INTSXP g0c0 [NAM(3)] 10 : 1 (compact)
> x[6] <- 77L
> .Internal(inspect(x))
@55f7a096e5e8 13 INTSXP g0c4 [NAM(1)] (len=10, tl=0) 10,9,8,7,6,...
> print(x) # 10 9 8 7 6 77 4 3 2 1
[1] 10 9 8 7 6 77 4 3 2 1
> sum(x)
[1] 127
,为此10 : 1
使用快捷方式。有关ALTREP的更多信息(包括参考资料),请参见here。
目前,唯一的解决方案似乎是避免更改function参数。
答案 1 :(得分:2)
这是我为此发布的elsewhere:
所以这里有几件事,其中@ltierney和/或@kalibera都涉及其中,但也许可以从更具体的可能的编码模式中受益。
问题的症结在于通过写入SEXP的DATAPTR寻址存储器的元素来内联修改SEXP的有效负载。是否可以做(是),是否可以安全地做而不确认/保证可以做(不,从不)。对于在同一块C / C ++代码中创建的对象,您可能会有这样的先验保证,但是您不会想要从R传递下来的对象。
具体来说,我不知道在不先检查INTEGER()
的情况下,是否可以在生活在R空间中的SEXP上写入MAYBE_SHARED()
返回的指针是否可行。如果MAYBE_SHARED(x)
返回FALSE,就可以了,您可以完全按照代码的方式继续写指针。
如果MAYBE_SHARED(x) == TRUE
,则需要复制,对副本进行操作,然后将其返回。当您使用C / C ++代码时,在获取指向事物的数据指针的级别上,那么您需要显式地导致该重复,保护新的重复结果等。
现在发生这种特殊情况的原因是在紧凑序列的情况下,除非R本身以特定的非默认方式构建,否则紧凑序列始终具有NAMED(x) == MAXNAMED
(即2)创作。正如卢克指出的那样,这可能会改变,但目前是设计使然。因此,即使在.call情况下,闭包并没有强制NAMED
计数,紧缩序列在进行内联修改之前也始终需要复制。尽管这是一个选择,我们可以在紧凑序列情况下有所作为,但卢克关于其他ALTREP的观点更为重要。
可能存在ALTREP SEXP,由于某种原因或其他原因,由INTEGER
返回的指针所指向的内存实际上不是可写内存。这些ALTREP类声明的方式是通过执行紧凑序列的相同操作,即将自身标记为“ IMMUTABLE”,即通过进行MARK_NOT_MUTABLE(x)
(当前将NAMED
设置为{{1} },但可以防止将来最终更改为引用计数)。这声明合同,必须在获取数据指针并将其写入的任何代码之前复制SEXP。
最终,我同意这是一种非常奇怪的意外行为,但这是由于未能遵守合同而一直存在的。过去在某些情况下忽略/放松可能是安全的(有点,但我仍然对此表示怀疑),但是由于ALTREP的出现,出于本主题中概述的原因,现在必须始终遵循它。
因此,要从一个既有的(R级)SEXP中获取dataptr并进行写入的所有所有代码,都必须遵循(或相当小心)的模式:
MAXNAMED
任何不执行此操作而写入未从SEXP检索到的数据指针的代码(即,从R端传来的任何事情)都已经违反了C-API合同,但是现在也致命ALTREP不安全,因为显示了激励人的例子。
再记住一次,紧缩序列的行为可能有所不同,因此该代码恰好起作用,但是其他ALTREP类(Luke提到了由内存映射文件支持的ALTREPS)却无法,因此紧缩序列的行为是其实不是这里的问题。
我希望这会有所帮助,并使事情变得更清楚。
最佳