Изменение переменной R, присвоенной литералу через C, приводит к странному поведению

#r #rcpp

#r #rcpp

Вопрос:

Кто-нибудь знает, что происходит со следующим кодом R?

 library(inline)

increment <- cfunction(c(a = "integer"), "
  INTEGER(a)[0]  ;
  return R_NilValue;
")

print_one = function(){
  one = 0L
  increment(one)
  print(one)
}

print_one() # prints 1
print_one() # prints 2
  

Напечатанные результаты равны 1, 2. Замена one = 0L на one = integer(1) дает результат 1, 1.

Просто для пояснения. Я знаю, что передаю переменную one по ссылке на функцию C, поэтому ее значение должно измениться (стать 1 ). Чего я не понимаю, так это того, почему сброс one = 0L , похоже, не имеет никакого эффекта после первого вызова print_one (второй вызов print_one печатает 2 вместо 1 ).

Комментарии:

1. R использует семантику вызова по значению с реализацией копирования при записи, и если вы пишете код на C, который взаимодействует с R, ожидается, что вы будете выполнять копирование при записи самостоятельно. Вам не разрешается изменять аргументы функции подобным образом.

2. Мне просто любопытно, что здесь происходит. Почему при последующих вызовах print_one сброс one=0L не имеет никакого эффекта (по крайней мере, кажется, что это происходит)?

3. Я не думаю, что этот вопрос имеет какое-либо отношение к пакету Rcpp (или встроенному). Удален тег rcpp.

4. rcpp — это пакет-преемник inline, написанный тем же автором (Дирк Эдделбюттель). Общая рекомендация заключается в том, что вы должны использовать rcpp в будущем

Ответ №1:

Это действительно (как намекнул последний комментарий) имеет мало общего с Rcpp. На самом деле это в основном касается .C() интерфейса, используемого старым inline пакетом, и здесь по cfunction подходу в вопросе.

Это приводит к двум ответам.

Во-первых, консенсус среди разработчиков R заключается в том, что .C() он устарел и больше не должен использоваться. Соответствующие инструкции можно найти в списках r-devel и r-package-devel . .C() использует простые старые типы в качестве указателей в интерфейсе, поэтому здесь целочисленное значение передается как int* по ссылке и может быть изменено.

Если мы переключимся на Rcpp uses и, следовательно, на базовый .Call() интерфейс, используя только SEXP типы для ввода и вывода, тогда int по ссылке не передается. Таким образом, код ведет себя и печатает только 0 :

 Rcpp::cppFunction("void increment2(int a) { a  ; }")

print_two <- function(){
  two <- 0L
  increment2(two)
  print(two)
}

print_two() # prints 0
print_two() # prints 0
  

Наконец, Rcpp (capital R), конечно, не является «преемником» inline (поскольку он делает намного больше, чем inline , но он среди всех своих функциональных возможностей является (примерно с 2013 года) квази-заменой атрибутов inline in Rcpp. Итак, с Rcpp «как есть» примерно с 2013 года вам больше не нужны примеры и подход inline .