R 检查导致SEG故障的cpp代码

R 检查导致SEG故障的cpp代码,r,rcpp,R,Rcpp,我有一些cpp代码,它在一个调用了大约80k次的R函数中运行。它的测试套件全面且通过测试。它似乎运行良好的第一次调用60K次,然后在中间的某个地方,我得到一个Sebug。 *** Error in `/usr/lib/R/bin/exec/R': malloc(): memory corruption: 0x00000000047150f0 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7

我有一些cpp代码,它在一个调用了大约80k次的R函数中运行。它的测试套件全面且通过测试。它似乎运行良好的第一次调用60K次,然后在中间的某个地方,我得到一个Sebug。

*** 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
s标记为
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;
}
// [[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;
}

为了解决主要问题,您会遇到看似随机的segfaults,因为您的代码包含未定义的行为——特别是数组边界冲突。由于您之前注意到C++是相当新的,因此,至少要阅读讨论这个特定错误的第一个答案是值得的。对于其他语言的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;
} 
但是,当我们用执行相同元素访问的
at()
成员函数替换
操作符[]
时,错误显而易见:

// [[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;
}
请注意,由
at
提供的附加边界检查确实会带来轻微的性能成本,因为此检查发生在运行时,因此即使在开发阶段使用
at
而不是
操作符[]
也是一个好主意,一旦您的代码经过彻底测试,如果需要更好的性能,那么最好恢复到
操作符[]


至于解决方案,第一种方法是保持
while
循环,只需添加对
i
值的检查:

while (i < n && x[i] == NA_INTEGER) {
    leading_na[i] = TRUE;
    i++;
} 
在这种情况下,选择使用
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;
} 
尽管迭代器是非常有用的设备,但我不确定在这种特殊情况下,迭代器是否比手动索引有任何好处,因此我建议使用前两种方法之一


与segfault无关,代码还有一些其他方面值得解决

  • 在R中,
    &
    |
    分别执行原子逻辑and和原子逻辑OR,而
    &
    |
    分别执行向量化逻辑and和向量化逻辑OR。在C++中,和& < /代码>和<代码> <代码> >在R中执行,但是<代码>和<代码>和<代码> <代码>是(原子)按位和(原子)按位或分别。碰巧,在上面的函数中使用
    &
    与使用
    &
    具有相同的效果,但是您需要解决这个问题,因为您的目的是使用逻辑运算,而不是按位运算
  • 这对于Rcpp/R的C API更为具体,但尽管使用
    x[i]==NA_INTEGER
    确实可以测试
    x[i]
    是否为
    NA
    ,但并非所有类型的行为都是这样的。IIRC,用NA_REAL检验任何事物是否相等总是错误的,即使是NA_REAL==NA_REAL;对于非整数算术类型(数字和复杂(
    REALSXP
    /
    CPLXSXP
    ),您很可能还需要检查值是否为
    NaN
    )。Rcpp根据对象类型提供了几种不同的方法。对于任何存储类型的向量,
    Rcpp::is_na(x)
    将返回与
    x
    大小相同的逻辑向量。对于原子值,我通常使用
    Rcpp::traits::is_na(x[I])
    -
    REALSXP
    表示
    double
    INTSXP
    表示
    int
    CPLXSXP
    表示
    Rcomplex
    ,等等。但是,我认为您可以等效地使用向量对应的静态成员函数,例如,
    Rcpp::NumericVector::is_na(x[I])
    ,在这种情况下,您不需要记住各种
    SEXPTYPE
    s 严格来说,C++或C中没有<代码>真<代码> >代码>伪<代码>;这些(大概)是由R的API提供的方便TypeDef,所以请注意它们并不存在于R的后端之外。当然,可以在您的Rcpp代码中随意使用它们,因为它们的行为显然符合预期,但大多数人即使在使用Rcpp时也坚持标准
    true
    false
  • 有点吹毛求疵,但是您的
    leading_na
    函数声明了一个局部变量,也被命名为
    leading_na
    ,这有点让人困惑,或者至少是非正统的
  • 在处理对象的大小时,请考虑使用
    std::size_t
    (标准C++)或
    R_xlen_t
    (特定于R API),例如在以下表达式中:
    int n=x.size()。这些是无符号整数类型,其大小应足以存储任何对象的长度,其中as
    int
    是一种有符号整数类型,它可能足够,也可能不够(通常是)。99.9%的情况下,最糟糕的情况是,在使用
    int
    s时,您会收到一些额外的编译器警告(而不是错误),而不是在表达式中使用
    for(int i=0;i
    等其他两个警告。在极少数情况下,可能会有更严重的影响,如信号
    leading_na2(rep(NA, 5))
    # 0
    # 1
    # 2
    # 3
    # 4
    # Error: index out of bounds 
    
    while (i < n && x[i] == NA_INTEGER) {
        leading_na[i] = TRUE;
        i++;
    } 
    
    for (int i = 0; i < n && x[i] == NA_INTEGER; i++) {
        leading_na[i] = TRUE;
    }
    
    // [[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;
    }