Просмотрите код cpp, вызывающий segfault
У меня есть некоторый код cpp, который выполняется внутри функции R, которая вызывается около 80 тысяч раз. Это набор тестов, полный и проходящий. Кажется, он работает нормально первые 60 000 раз, когда он вызывается, а затем где-то посередине я получаю сегфо.
*** 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);
}
Копирует функциональность 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;
}
1 ответ
Чтобы решить основную проблему, вы получаете, казалось бы, случайные ошибки, потому что ваш код содержит неопределенное поведение - в частности, нарушение границы массива. Поскольку ранее вы отмечали, что вы довольно плохо знакомы с C++, вам стоит прочитать хотя бы первый ответ на этот вопрос, в котором обсуждается эта конкретная ошибка. 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()
Функция-член, которая выполняет тот же элемент доступа, но также выполняет проверку границ во время выполнения, ошибка очевидна:
// [[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;
}
Хотя итераторы являются очень полезными устройствами, я не уверен, что они обеспечивают какую-либо выгоду по сравнению с ручной индексацией в данном конкретном случае, поэтому я бы рекомендовал использовать один из первых двух подходов.
Не связанные с segfault, есть несколько других аспектов вашего кода, которые стоит рассмотреть.
- В R,
&&
а также||
выполнять атомное логическое И и атомное логическое ИЛИ, соответственно, при этом&
а также|
выполнить векторизованное логическое И и векторизованное логическое ИЛИ соответственно. В C++&&
а также||
вести себя так же, как в R, но&
а также|
являются (атомарными) побитовым И и (атомарным) побитовым ИЛИ, соответственно. Просто случайно, используя&
имел тот же эффект, что и при использовании&&
в вашей функции выше, но вы захотите исправить это, поскольку вы намеревались использовать логическую операцию, а не побитовый аналог. - Это более конкретно для C API Rcpp / R, но хотя и использует
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])
-REALSXP
заdouble
,INTSXP
заint
,CPLXSXP
заRcomplex
, и так далее. Тем не менее, я думаю, что вы можете эквивалентно использовать соответствующую статическую функцию-член вектора, напримерRcpp::NumericVector::is_na(x[i])
и т. д., в этом случае вам не нужно запоминать различныеSEXPTYPE
s. - Собственно говоря нет
TRUE
или жеFALSE
в C++ или C; это (предположительно) удобные определения типов, предоставляемые API R, так что просто имейте в виду, что они не существуют вне бэкэнда R. Конечно, не стесняйтесь использовать их в своем коде Rcpp, поскольку они явно ведут себя как задумано, но большинство людей придерживаются стандартаtrue
а такжеfalse
даже при работе с Rcpp. - Вид придурка, но твой
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 }
, В редких случаях возможны худшие последствия, такие как целочисленное переполнение со знаком (что также является неопределенным поведением), поэтому просто помните об этой удаленной возможности.
Этот ответ в некотором роде превратился в скандал с обзором кода / soapbox, но, надеюсь, вы найдете там полезную информацию.