2016-08-28 44 views
1

我有一些cpp代碼在R函數中運行,這被稱爲約80k次。它的測試套件是全面和通過的。它似乎在第一次調用它的前60k時運行良好,然後在中間的某個地方,我得到了段錯誤。查看導致段錯誤的cpp代碼

*** 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; 
} 

返回一個LogicalVector(例如TRUE/FALSE向量),其中拖尾NA s的標記爲TRUE

// [[Rcpp::export]] 
LogicalVector trailing_na(IntegerVector x) { 
    LogicalVector trailing_na = leading_na(rev(x)); 
    return rev(trailing_na); 
} 

複製的功能從動物園包:

// [[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; 
} 
+2

你的意思是使用*位*和這裏:'(i> 0)&(x [i] == NA_INTEGER)&(lna [i]!= TRUE)'?因爲*邏輯* AND在C++中是'&&'。 – nrussell

+1

你如何調用你的'max_x_pos'函數?因爲這行不是很好定義 - 'while(y [i] == NA_INTEGER){i ++; } - 當你的輸入全部是「NA」時。爲了看到這個,把它改爲'while(y.at(i)== NA_INTEGER){i ++; }'來獲得邊界檢查。當你調用'max_x_pos(c(NA,NA))'時,你會得到'錯誤:索引超出範圍'。您應該在'i'上添加一個限制,例如'while(i nrussell

+1

其實,你的'leading_na'函數有上面描述的相同的錯誤。 – nrussell

回答

6

要解決的首要問題,你得到看似隨意的段錯誤,因爲你的代碼包含未定義行爲 - 特別是數組邊界違規。由於您之前提到您對C++相當陌生,因此您至少應該閱讀討論此特定錯誤的第一個答案to this question是值得的。 UB可能是一個簡單的概念,用於包裝來自其他語言來C或C++的人的頭腦,這主要是因爲它並不總是以錯誤的形式表現出來。行爲是字面上未定義,所以沒有辦法知道結果會是什麼,也不應該期望行爲在跨平臺,編譯器甚至編譯器版本是一致的。

我會用你的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提供不來在性能略有下降,因爲該檢查發生在運行,所以儘管它可以是一個好主意,在開發階段使用at代替operator[],一旦你的代碼已經過全面測試,假設需要更好的性能,那麼回到operator[]可能是一個好主意。


至於解決辦法,第一是保持while循環,只是對i值增加一個檢查:

while (i < n && x[i] == NA_INTEGER) { 
    leading_na[i] = TRUE; 
    i++; 
} 

請注意,我寫了i < n && x[i] == NA_INTEGERx[i] == NA_INTEGER && i < n 。由於&&執行短路評估,因此當i < n在第一個版本中評估爲false時,表達式x[i] == NA_INTEGER而非評估 - 這很好,因爲如我們所見,這是未定義的行爲。

另一種選擇是使用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; 
} 

雖然迭代器是非常有用的設備,我在這種特殊情況下,他們不確定他們是否提供了超過手動索引的好處,所以我建議使用前兩種方法之一。


與段錯誤無關,還有一些其他方面的代碼值得處理。

  1. 在R,&&||分別分別執行原子邏輯與和邏輯原子OR,而&|執行矢量化邏輯與和矢量化邏輯OR。在C++中,&&||表現爲它們中的R做,但&|是(原子)按位 AND和(原子)按位 OR,分別。恰巧,使用&與上面的函數使用&&具有相同的效果,但您會想要解決此問題,因爲您的意圖是使用邏輯操作,而不是按位對應。
  2. 這對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<SEXPTYPE>(x[i])-0 REALSXP代替double,INTSXP代替int,CPLXSXP代替Rcomplex等等。不過,我認爲你可以等價地使用向量的相應靜態成員函數,例如Rcpp::NumericVector::is_na(x[i])等,在這種情況下,您不需要記憶各種SEXPTYPE
  3. 嚴格來說,C++或C中沒有TRUEFALSE;這些(可能是)由R的API提供的便利類型定義,所以請注意,它們不存在R的後端之外。當然,可以隨意在你的Rcpp代碼中使用它們,因爲它們明顯表現出預期的效果,但即使在使用Rcpp時,大多數人仍堅持使用標準truefalse
  4. 種類繁多的挑選,但你的leading_na函數聲明一個局部變量,也被命名爲leading_na,這有點混淆,或者至少是非正統的。
  5. 考慮在處理對象大小時使用std::size_t(標準C++)或R_xlen_t(R API特定),例如在以下表達式中:int n = x.size();。這些是無符號的整數類型,它應該足夠大以存儲任何對象的長度,其中int有符號的整數類型,它可能是或可能不夠(通常是)。 99.9%的時間會發生最糟糕的情況是,當使用int s而不是像for (int i = 0; i < x.size(); i++) { // whatever }這樣的表達式時,您將獲得一些額外的編譯器警告(不是錯誤)。在極少數情況下,可能會有更糟的反響,比如帶符號的整數溢出(這也是未定義的行爲),所以只需要注意這種遠程可能性。

這個答案變成了一個代碼審查/肥皂箱咆哮,但希望你在那裏找到一些有用的信息。

+2

完成的討論 - +1。 –

+0

謝謝 - 還有編輯。這可能需要我多收幾遍才能收集其餘的拼寫錯誤,但這就是我想寫一篇文章,我想... – nrussell

+0

我看到你建議使用for循環而不是while循環。我的想法是for循環會遍歷整個向量,while循環只會在條件匹配時纔會執行一個循環。對於很長的矢量,這可能會對性能產生影響嗎? –

相關問題