为什么某些内存地址报告为常量而其他内存地址发生变化?

Why are some memory addresses reported constant while others change?

我一直在尝试使用 data.table::address.Internal(address()) 来跟踪内存中的各种对象,但我注意到有些对象 return 每次都使用相同的地址,而其他对象几乎总是不同的。这是怎么回事?

我注意到像列表(data.tables、data.frames 等)这样的对象的地址保持不变(正如这些函数所报告的那样),而如果我尝试通过 [ 到列表中,即 address(lst[1]) 我几乎每次都会得到不同的结果。另一方面,lst[[1]] return 是相同的值,address(pi) 等常量的地址保持不变,而 address(1) 是易变的。为什么会这样?

## Create some data.tables of different sizes and plot the addresses
library(data.table)
par(mfrow = c(2,2))
for (i in 2:5) {
    dat <- data.table(a=1:10^i)
    ## Constants
    addr1 <- address(dat)
    addr2 <- address(dat[[1]])
    addr3 <- address(dat$a)  # same as addr2
    ## Vary
    addrs <- replicate(5000, address(dat[1]))
    plot(density(as.integer(as.hexmode(addrs))), main=sprintf("N: %g", nrow(dat)))
    abline(v=as.integer(as.hexmode(c(addr1, addr2, addr3))), col=1:3, lwd=2, lty=1:3)
    legend("topleft", c("dat", "dat[[1]]", "dat$a"), col=1:3, lwd=2, lty=1:3)
}

这里有一些我所说的不同尺寸的例子 data.tables。它们只是 address(dat[1]) 结果的密度(转换为整数),并且这些行对应于 data.table.

的常量地址

首先,我可以复制你的结果,所以我做了一些调查并深入研究了一些代码。

当您使用 dat[1] 访问 dat 的第一个成员时,您实际上是在创建一个由 data[[1]]dat$a 中的 list 组成的切片。要获取切片,R 首先复制列表,然后 returns 您想要的切片。

所以 - 基本上 - 你会看到你所看到的,因为 [] 语法用于索引 returns 包含 dat [=42= 的第一个元素的切片] 这是 dat$a 的副本,它将位于任意内存位置。

[[]] 语法 returns 对实际列表的引用,即 data.tabledata.frame 中的列,因此其地址是不变的(或至少直到您更改该列表的成员。

这可能会造成混淆,因为当然 dat[1] = 6 或类似操作会改变数据结构中列表的值。但是,如果您在进行此类更改之前和之后查看 address(dat[[1]]),您会注意到实际上引用现在指向不同的列表(副本),例如

> dat <- data.table(a=1:10000)
> dat
           a
    1:     1
    2:     2
    3:     3
    4:     4
    5:     5
   ---      
 9996:  9996
 9997:  9997
 9998:  9998
 9999:  9999
10000: 10000
> address(dat[[1]])
[1] "000000000CF389D8"
> address(dat[[1]])
[1] "000000000CF389D8"
> dat[1] = 100
> address(dat[[1]])
[1] "000000000D035B38"
> dat
           a
    1:   100
    2:     2
    3:     3
    4:     4
    5:     5
   ---      
 9996:  9996
 9997:  9997
 9998:  9998
 9999:  9999
10000: 10000
> 

查看 data.frame(而不是 data.table)的源代码,执行切片索引 ([]) is here, whereas the direct indexing ([[]]) is here. You can see that the latter is simpler and to cut a long story short, the former returns a copy. If you change a slice directly (e.g. dat[1] = 5), there is some logic here 的代码处理确保数据框现在引用更新后的副本。