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

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#中的值类型,那么任何赋值都会创建新的整数。因此,每个整数都将有自己的地址。 - Lukasz Szczygielek
1
实际上,即使两次运行obj_addr(x[1]),也应该得到不同的结果,因为每个新整数都有自己的地址。 - Bas
@Bas 我测试了你提到的内容,也就是连续运行obj_addr(x[1]),确实如此,每次R都会返回不同的结果(不同的内存地址)。但我不明白为什么会这样,因为在我看来,我没有分配任何东西,所以我没有创建新对象(显然,如果创建了新对象,那么它们将有一个新地址,因为在R中对象是不可变的)。对我来说,obj_addr(x[1])意味着我只是读取一个已经存在的对象。 - user17911
2个回答

5
任何R对象都是指向“多对象”(结构体)的C指针(称为 SEXP )。这包括关于R对象所需的信息(例如 length ,引用数量-知道何时复制对象-等等),以及我们可以访问的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(),而不是绑定到xy的数组的第一个元素的地址。


非常感谢您的帮助和关注我的问题。事实上,这正是我不知道的,也就是说,“Extract”通过检索值确实创建了一个新对象。正如我所说,我真的是R的初学者!非常感谢您的时间和描述。 - user17911

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接