Почему две ссылки на один и тот же вектор возвращают разные адреса памяти для каждого элемента вектора?

9

Я изучаю R и в настоящее время я читаю эту книгу . Чтобы убедиться, что я понимаю концепцию, я провел следующий тест, который оказался довольно запутанным для меня, и я был бы признателен, если бы вы могли уточнить его. Вот тест, который я запускал непосредственно в R-оболочке с терминала (не используя RStudio или Emacs ESS).

> library(lobstr)
>
> x <- c(1500,2400,8800)
> y <- x
> ### So the following two lines must return the same memory address
> obj_addr(x)
[1] "0xb23bc50"
> obj_addr(y)
[1] "0xb23bc50"
> ### So as I expected, indeed both x and y point to the same memory 
> ### location: 0xb23bc50
>
>
>
> ### Now let's check that each element can be referenced by the same
> ### memory address either by using x or y
> x[1]
[1] 1500
> y[1]
[1] 1500
> obj_addr(x[1])
[1] "0xc194858"
> obj_addr(y[1])
[1] "0xc17db88"
> ### And here is exactly what I don't understand: x and y point 
> ### to the same memory address, so the same must be true for 
> ### x[1] and y[1]. So how come I obtain two different memory
> ### addresses for the same element of the same vector?
>
>
>
> x[2]
[1] 2400
> y[2]
[1] 2400
> obj_addr(x[2])
[1] "0xc15eca0"
> obj_addr(y[2])
[1] "0xc145d30"
> ### Same problem!
>
>
>
> x[3]
[1] 8800
> y[3]
[1] 8800
> obj_addr(x[3])
[1] "0xc10e9b0"
> obj_addr(y[3])
[1] "0xc0f78e8"
> ### Again the same problem: different memory addresses

Не могли бы вы сказать мне, где моя ошибка и что я неправильно понял в этой проблеме?

user17911
источник
1
Я не знаю R, но на других языках у вас есть значения и ссылочные типы. Если целое число является типом значения, как в C ++ или C #, то любое присваивание создаст новое целое число. Таким образом, каждое целое число будет иметь собственный адрес.
Хостел
1
Действительно, даже повторный запуск obj_addr(x[1])должен давать разные результаты, поскольку каждое новое целое число будет иметь свой собственный адрес.
Бас
@Bas я протестировал то, что вы упомянули, то есть, работает последовательно obj_addr (x [1]), и действительно, делая так, R возвращает каждый раз другой результат (другой адрес памяти). Но я не понимаю почему, потому что, как мне кажется, я ничего не назначаю, поэтому я не создаю новый объект (для которого, очевидно, будет новый адрес, поскольку объекты неизменны в R). Для меня obj_addr (x [1]) означает, что я просто читаю уже существующий объект.
user17911

Ответы:

5

Любой объект R является C (указатель называется SEXPна) "multi-object" ( struct). Это включает в себя информацию (которую R должен использовать, например length, количество ссылок - чтобы узнать, когда копировать объект - и более) об объекте R, а также фактические данные объекта R, к которым у нас есть доступ.

lobstr::obj_addrпредположительно, возвращает адрес памяти, на который SEXPуказывает точка. Эта часть памяти содержит как информацию об объекте R, так и данные о нем. Из среды R мы не можем / не должны обращаться к (указателю на) памяти фактических данных в каждом объекте R.

Как отмечает Адам в своем ответе, функция [ копирует n-й элемент данных, содержащихся в объекте C, в новый объект C и возвращает его SEXPуказатель на R. При каждом [вызове создается новый объект C и возвращается в R.

Мы не можем получить доступ к адресу памяти каждого элемента фактических данных нашего объекта через R. Но немного поиграв, мы можем отследить соответствующие адреса, используя C api:

Функция для получения адресов:

ff = inline::cfunction(sig = c(x = "integer"), body = '
             Rprintf("SEXP @ %p\\n", x);

             Rprintf("first element of SEXP actual data @ %p\\n", INTEGER(x));

             for(int i = 0; i < LENGTH(x); i++) 
                 Rprintf("<%d> @ %p\\n", INTEGER(x)[i], INTEGER(x) + i);

             return(R_NilValue);
     ')

И применяя к нашим данным:

x = c(1500L, 2400L, 8800L)  #converted to "integer" for convenience
y = x

lobstr::obj_addr(x)
#[1] "0x1d1c0598"
lobstr::obj_addr(y)
#[1] "0x1d1c0598"

ff(x)
#SEXP @ 0x1d1c0598
#first element of SEXP actual data @ 0x1d1c05c8
#<1500> @ 0x1d1c05c8
#<2400> @ 0x1d1c05cc
#<8800> @ 0x1d1c05d0
#NULL
ff(y)
#SEXP @ 0x1d1c0598
#first element of SEXP actual data @ 0x1d1c05c8
#<1500> @ 0x1d1c05c8
#<2400> @ 0x1d1c05cc
#<8800> @ 0x1d1c05d0
#NULL

Последовательная разница в памяти между элементами данных нашего объекта равна размеру intтипа:

diff(c(strtoi("0x1d1c05c8", 16), 
       strtoi("0x1d1c05cc", 16), 
       strtoi("0x1d1c05d0", 16)))
#[1] 4 4

Используя [функцию:

ff(x[1])
#SEXP @ 0x22998358
#first element of SEXP actual data @ 0x22998388
#<1500> @ 0x22998388
#NULL
ff(x[1])
#SEXP @ 0x22998438
#first element of SEXP actual data @ 0x22998468
#<1500> @ 0x22998468
#NULL

Это могло бы быть более чем необходимым подробным ответом и упрощенным с точки зрения фактических технических особенностей, но, мы надеемся, предлагает более ясную "большую" картину.

alexis_laz
источник
Потрясающие! Я действительно благодарю вас за такое подробное и ясное объяснение для таких людей, как я, которые являются новичками в R. Кроме того, ваш пример очень впечатляет, демонстрируя гибкость R и его возможное мощное взаимодействие с другими языками программирования. Большое спасибо за ваше время и вашу помощь.
user17911
3

Это один из способов взглянуть на это. Я уверен, что есть более технический взгляд. Помните, что в R почти все является функцией. Это включает в себя функцию извлечения [. Вот эквивалентное утверждение для x[1]:

> `[`(x, 1)
[1] 1500

Итак, вы выполняете функцию, которая возвращает значение (зацените ?Extract). Это значение является целым числом. Когда вы запускаете obj_addr(x[1]), он оценивает функцию, x[1]а затем выдает obj_addr()возврат этой функции, а не адрес первого элемента массива, который вы связали с обоими xи y.

Адам
источник
Большое спасибо за вашу помощь и ваше внимание к моей проблеме. На самом деле это то, чего я не знал, то есть получение значения с помощью «Извлечения» действительно создает новый объект. Как я уже сказал, я действительно новичок в R! Большое спасибо за ваше время и ваше описание.
user17911