要解決的首要問題,你得到看似隨意的段錯誤,因爲你的代碼包含未定義行爲 - 特別是數組邊界違規。由於您之前提到您對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_INTEGER
和不x[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;
}
雖然迭代器是非常有用的設備,我在這種特殊情況下,他們不確定他們是否提供了超過手動索引的好處,所以我建議使用前兩種方法之一。
與段錯誤無關,還有一些其他方面的代碼值得處理。
- 在R,
&&
和||
分別分別執行原子邏輯與和邏輯原子OR,而&
和|
執行矢量化邏輯與和矢量化邏輯OR。在C++中,&&
和||
表現爲它們中的R做,但&
和|
是(原子)按位 AND和(原子)按位 OR,分別。恰巧,使用&
與上面的函數使用&&
具有相同的效果,但您會想要解決此問題,因爲您的意圖是使用邏輯操作,而不是按位對應。
- 這對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
。
- 嚴格來說,C++或C中沒有
TRUE
或FALSE
;這些(可能是)由R的API提供的便利類型定義,所以請注意,它們不存在R的後端之外。當然,可以隨意在你的Rcpp代碼中使用它們,因爲它們明顯表現出預期的效果,但即使在使用Rcpp時,大多數人仍堅持使用標準true
和false
。
- 種類繁多的挑選,但你的
leading_na
函數聲明一個局部變量,也被命名爲leading_na
,這有點混淆,或者至少是非正統的。
- 考慮在處理對象大小時使用
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 }
這樣的表達式時,您將獲得一些額外的編譯器警告(不是錯誤)。在極少數情況下,可能會有更糟的反響,比如帶符號的整數溢出(這也是未定義的行爲),所以只需要注意這種遠程可能性。
這個答案變成了一個代碼審查/肥皂箱咆哮,但希望你在那裏找到一些有用的信息。
你的意思是使用*位*和這裏:'(i> 0)&(x [i] == NA_INTEGER)&(lna [i]!= TRUE)'?因爲*邏輯* AND在C++中是'&&'。 – nrussell
你如何調用你的'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
其實,你的'leading_na'函數有上面描述的相同的錯誤。 – nrussell