¿Por qué dos referencias al mismo vector devuelven direcciones de memoria diferentes para cada elemento del vector?

9

Estoy aprendiendo R y actualmente estoy leyendo este libro . Para asegurarme de que entiendo el concepto, realicé la siguiente prueba que resultó ser bastante confusa para mí y agradecería si pudiera aclararla. Aquí está la prueba, que ejecuté directamente en el shell R desde el terminal (sin usar RStudio o 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

¿Podría decirme dónde está mi error y qué he entendido mal en este problema?

usuario17911
fuente
1
No sé R pero en otros idiomas tienes valores y tipos de referencia. Si el entero es un tipo de valor como en C ++ o C #, cualquier asignación creará un nuevo entero. Entonces cada entero tendrá su propia dirección.
Albergue
1
De hecho, incluso ejecutar obj_addr(x[1])dos veces debería darle resultados diferentes, ya que cada nuevo entero tendrá su propia dirección.
Bas
@Bas probé lo que mencionaste, es decir, ejecutar sucesivamente obj_addr (x [1]), y de hecho, R devuelve cada vez un resultado diferente (dirección de memoria diferente). Pero no entiendo por qué, porque, según me parece, no asigno nada, por lo que no creo un nuevo objeto (para lo cual obviamente habrá una nueva dirección ya que los objetos son inmutables en R). Para mí obj_addr (x [1]) significa que solo estoy leyendo un objeto ya existente.
user17911

Respuestas:

5

Cualquier objeto R es una C (llamada puntero SEXP - a) "multi-objeto" ( struct). Esto incluye información (que R necesita para operar, por ejemplo length, número de referencias -para saber cuándo copiar un objeto- y más) sobre el objeto R y, también, los datos reales del objeto R al que tenemos acceso.

lobstr::obj_addr, presumiblemente, devuelve la dirección de memoria que un SEXP apunta. Esa parte de la memoria contiene tanto la información como los datos del objeto R. Desde el entorno R no podemos / no necesitamos acceder a la memoria (puntero a) de los datos reales en cada objeto R.

Como Adam señala en su respuesta, la función [ copia el enésimo elemento de los datos contenidos en el objeto C a un nuevo objeto C y devuelve su SEXPpuntero a R. Cada vez que [se llama, se crea un nuevo objeto C y se devuelve a R.

No podemos acceder a la dirección de memoria de cada elemento de los datos reales de nuestro objeto a través de R. Pero jugando un poco, podemos rastrear las direcciones respectivas usando la API C:

Una función para obtener las direcciones:

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);
     ')

Y aplicando a nuestros datos:

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

La diferencia de memoria sucesiva entre los elementos de datos de nuestro objeto es igual al tamaño del inttipo:

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

Usando la [función:

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

Esta podría ser una respuesta extensa más de la necesaria y es simplista en cuanto a los tecnicismos reales, pero, con suerte, ofrece una imagen "más grande" más clara.

alexis_laz
fuente
¡Increíble! Realmente les agradezco mucho por una explicación tan detallada y clara para personas como yo que son completamente principiantes en R. Además, su ejemplo es muy impresionante al mostrar la flexibilidad de R y sus posibles interacciones poderosas con otros lenguajes de programación. Muchas gracias por su tiempo y su ayuda.
user17911
3

Esta es una forma de verlo. Estoy seguro de que hay una visión más técnica. Recuerde que en R, casi todo es una función. Esto incluye la función de extracto, [. Aquí hay una declaración equivalente a x[1]:

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

Entonces, lo que está haciendo es ejecutar una función que devuelve un valor (verifique ?Extract). Ese valor es un entero. Cuando ejecuta obj_addr(x[1]), está evaluando la función x[1]y luego le da el obj_addr()retorno de esa función, no la dirección del primer elemento de la matriz que vincula a ambosx y y.

Adán
fuente
Muchas gracias por su ayuda y su atención a mi problema. De hecho, esto es lo que no sabía, es decir, recuperar un valor mediante "Extracto" de hecho crea un nuevo objeto. Como dije, ¡soy realmente un principiante en R! Muchas gracias por tu tiempo y tu descripción.
user17911