我有一些在R函数中运行的cpp代码,调用大约80k次。它的测试套件是全面的并且通过。它的前60k次似乎运行良好,然后在中间的某个地方,我得到了一个段错误。
*** Error in `/usr/lib/R/bin/exec/R': malloc(): memory corruption: 0x00000000047150f0 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7f684462e725]
/lib/x86_64-linux-gnu/libc.so.6(+0x819be)[0x7f68446389be]
/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f684463a5a4]
/usr/lib/R/lib/libR.so(Rf_allocVector3+0x70d)[0x7f6844cd617d]
... # more
以下是我的一些代码示例,你能看到它有什么问题吗?
返回LogicalVector
(例如TRUE
/ FALSE
向量),其中前导NA
被标记为TRUE
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
LogicalVector leading_na(IntegerVector x) {
int n = x.size();
LogicalVector leading_na(n);
int i = 0;
while(x[i] == NA_INTEGER) {
leading_na[i] = TRUE;
i++;
}
return leading_na;
}
返回LogicalVector
(例如TRUE
/ FALSE
向量),其中尾随NA
被标记为TRUE
// [[Rcpp::export]]
LogicalVector trailing_na(IntegerVector x) {
LogicalVector trailing_na = leading_na(rev(x));
return rev(trailing_na);
}
从zoo包中复制na.locf(x, na.rm = TRUE)
的功能:
// [[Rcpp::export]]
IntegerVector na_locf(IntegerVector x) {
int n = x.size();
LogicalVector lna = leading_na(x);
for(int i = 0; i<n; i++) {
if((i > 0) & (x[i] == NA_INTEGER) & (lna[i] != TRUE)) {
x[i] = x[i-1];
}
}
return x;
}
返回带有数字的向量中的最后一个位置:
// [[Rcpp::export]]
int max_x_pos(IntegerVector x) {
IntegerVector y = rev(x);
int n = x.size();
LogicalVector leading_na(n);
int i = 0;
while(y[i] == NA_INTEGER) {
i++;
}
return n-i;
}
答案 0 :(得分:6)
要解决主要问题,您将获得看似随机的段错误,因为您的代码包含未定义的行为 - 特别是数组边界违规。既然你之前已经注意到你是C ++的新手,那么你至少应该阅读第一个回答这个特定错误的答案to this question是值得的。对于那些从其他语言转向C或C ++的人来说,UB可能是一个很滑的概念,主要是因为它并不总是以错误的形式表现出来。行为字面上 未定义,因此无法知道结果是什么,也不应期望行为在平台,编译器甚至编译器之间保持一致版本。
我将使用您的leading_na
函数进行演示,但max_x_pos
函数存在同样的问题:
// [[Rcpp::export]]
Rcpp::LogicalVector leading_na(Rcpp::IntegerVector x) {
int n = x.size();
Rcpp::LogicalVector leading_na(n);
int i = 0;
while (x[i] == NA_INTEGER) {
// ^^^^
Rcpp::Rcout << i << "\n";
leading_na[i] = TRUE;
i++;
}
return leading_na;
}
由于没有强制约束i < n
的任何内容,当x
仅包含NA
元素时,代码会继续评估x[n]
(以及可能的后续索引) ),这是未定义的。但是,对于较小的向量,这在我的机器上运行得很好(我最终设法使其更大的输入崩溃),这正是为什么难以识别与UB相关的错误:
leading_na(rep(NA, 5))
# 0
# 1
# 2
# 3
# 4
# [1] TRUE TRUE TRUE TRUE TRUE
但是,当我们用operator[]
成员函数替换at()
时,它会执行相同的元素访问,但在运行时会also does bounds checking,但错误很明显:
// [[Rcpp::export]]
Rcpp::LogicalVector leading_na2(Rcpp::IntegerVector x) {
int n = x.size();
Rcpp::LogicalVector leading_na(n);
int i = 0;
while (x.at(i) == NA_INTEGER) {
Rcpp::Rcout << i << "\n";
leading_na[i] = TRUE;
i++;
}
return leading_na;
}
然后
leading_na2(rep(NA, 5))
# 0
# 1
# 2
# 3
# 4
# Error: index out of bounds
请注意,at
提供的附加边界检查会产生轻微的性能损失,因为此检查会在运行时进行,因此尽管使用{最好是一个好主意在开发阶段{1}}而不是at
,一旦您的代码经过全面测试,最好还原到operator[]
,假设需要更好的性能。
对于解决方案,第一个是保留operator[]
循环,只需添加对while
的值的检查:
i
请注意,我写了while (i < n && x[i] == NA_INTEGER) {
leading_na[i] = TRUE;
i++;
}
和不是 i < n && x[i] == NA_INTEGER
。由于x[i] == NA_INTEGER && i < n
执行短路评估,当&&
在第一个版本中评估为i < n
时,表达式false
不评估 - 其中是好的,因为正如我们所看到的那样,这是未定义的行为。
另一种选择是使用x[i] == NA_INTEGER
循环,这样可以更好地提醒&#34;提醒&#34;根据我的经验,我们至少要检查我们的界限:
for
在这种情况下,选择使用for (int i = 0; i < n && x[i] == NA_INTEGER; i++) {
leading_na[i] = TRUE;
}
循环或while
循环并不重要,前提是您选择的内容都是正确写入的。
另一个选项(或两个)是使用迭代器而不是索引,在这种情况下,您可以使用for
循环或while
循环:
for
虽然迭代器是非常有用的设备,但我不确定它们在这种特殊情况下是否比手动索引提供任何好处,因此我建议使用前两种方法之一。
与段错误无关,您的代码还有一些其他方面值得解决。
// [[Rcpp::export]]
Rcpp::LogicalVector leading_na5(Rcpp::IntegerVector x) {
int n = x.size();
Rcpp::LogicalVector leading_na(n);
Rcpp::IntegerVector::const_iterator it_x = x.begin();
Rcpp::LogicalVector::iterator first = leading_na.begin(),
last = leading_na.end();
/*
while (first != last && *it_x++ == NA_INTEGER) {
*first++ = TRUE;
}
*/
for ( ; first != last && *it_x == NA_INTEGER; ++first, ++it_x) {
*first = TRUE;
}
return leading_na;
}
和&&
分别执行原子逻辑AND和原子逻辑OR,而||
和&
分别执行矢量化逻辑AND和矢量化逻辑OR 。在C ++中,|
和&&
的行为与在R中的行为相同,但||
和&
是(原子)按位 AND和(原子) 按位 OR。只是偶然,使用|
与在上面的函数中使用&
具有相同的效果,但是您需要修复它,因为您的意图是使用逻辑运算,而不是按位对应。 &&
测试x[i] == NA_INTEGER
是否为x[i]
,但并非所有类型都表现得像这个。 IIRC,对NA
的平等进行测试总是错误的,甚至是NA_REAL
;对于非整数算术类型(数字和复数(NA_REAL == NA_REAL
/ REALSXP
)),您很可能也想检查值是否为CPLXSXP
。 Rcpp根据对象类型提供了几种不同的方法。对于任何存储类型的向量,NaN
将返回与Rcpp::is_na(x)
大小相同的逻辑向量。对于原子值,我通常使用x
- Rcpp::traits::is_na<SEXPTYPE>(x[i])
用于REALSXP
,double
用于INTSXP
,int
用于CPLXSXP
,等等。但是,我认为你可以等效地使用向量的相应静态成员函数,例如Rcomplex
等,在这种情况下,您不需要记住各种Rcpp::NumericVector::is_na(x[i])
。 SEXPTYPE
或TRUE
;这些(可能是)R的API提供的便利类型定义,所以请注意它们不存在于R的后端之外。当然,您可以随意在Rcpp代码中使用它们,因为它们显然符合预期,但即使在使用Rcpp时,大多数人仍然遵守标准FALSE
和true
。 false
函数声明了一个名为leading_na
的局部变量,这有点令人困惑,或者至少是非正统的。 leading_na
(标准C ++)或std::size_t
(特定于R API),例如在此表达式中:R_xlen_t
。这些是无符号整数类型,它们应足够大以存储任何对象的长度,其中int n = x.size();
是签名整数类型,可能是也可能不是足够(通常是)。 99.9%的时间会发生最糟糕的情况是,在使用int
时,您将获得一些额外的编译器警告(而不是错误),而不是int
等表达式中的其他两个。在极少数情况下,可能会有更糟糕的反响,例如有符号整数溢出(这也是未定义的行为),所以请注意这种远程可能性。 这个答案变成了代码审查/肥皂盒咆哮,但希望你在那里找到一些有用的信息。