为什么对同一个向量的两个引用为向量的每个元素返回不同的内存地址?


9

我正在学习R,目前正在读这本书。为了确保我理解这个概念,我进行了以下测试,结果对我来说很困惑,如果您能澄清一下,我将不胜感激。这是测试,我直接从终端在R Shell中运行(不使用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

您能告诉我我的错误在哪里以及在这个问题上我被误解了吗?


1
我不了解R,但是在其他语言中,您具有值和引用类型。如果整数是C ++或C#中的值类型,则任何赋值都会创建新的整数。因此,每个整数都有自己的地址。
宿舍

1
确实,即使运行obj_addr(x[1])两次也会给您不同的结果,因为每个新整数都会有自己的地址。
巴斯

@Bas我测试了您提到的内容,即依次运行obj_addr(x [1]),实际上,这样做每次R都会返回不同的结果(不同的内存地址)。但是我不明白为什么,因为在我看来我什么都不分配,所以我不创建新的对象(显然,由于R中的对象是不可变的,因此将有一个新的地址)。对我来说,obj_addr(x [1])意味着我正在读取一个已经存在的对象。
user17911

Answers:


5

任何R对象都是C(指向a的指针SEXP)“多对象”(struct)。这包括有关R对象的信息(R需要操作,例如length,引用的数量-以便知道何时复制对象等),以及与R对象有关的信息,以及我们可以访问的R对象的实际数据。

lobstr::obj_addr,大概返回一个SEXP指向的内存地址。内存的该部分包含有关R对象的信息数据。在R环境中,我们可以/不需要访问每个R对象中实际数据的(指针)内存。

正如Adam在其答案中指出的那样,该函数将C对象中包含的数据的第n个元素[ 复制到新的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

这可能是超出所需的广泛答案,并且在实际技术上过于简单,但是希望可以提供更清晰的“大”图景。


太棒了!我真的非常感谢您为像我这样完全是R的初学者的人们提供如此详尽而清晰的解释。而且,您的示例在展示R的灵活性以及与其他编程语言的强大交互方面非常令人印象深刻。非常感谢您的时间和帮助。
user17911

3

这是一种查看方式。我敢肯定还有更多技术观点。请记住,在R中,几乎所有东西都是一个函数。其中包括提取功能[。这是等效的声明x[1]

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

因此,您正在执行的是一个返回值的函数(签出?Extract)。该值是整数。在运行时obj_addr(x[1]),它将评估函数x[1],然后为您obj_addr()提供该函数的返回值,而不是您同时绑定到x和的数组的第一个元素的地址y


非常感谢您的帮助和关注我的问题。的确,这是我所不知道的,也就是说,通过“提取”检索值确实创建了一个新对象。正如我所说的,我真的是R语言的初学者!非常感谢您的时间和描述。
user17911
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.