Dlaczego dwa odniesienia do tego samego wektora zwracają różne adresy pamięci dla każdego elementu wektora?

9

Uczę się języka R i obecnie czytam tę książkę . Aby upewnić się, że rozumiem tę koncepcję, przeprowadziłem następujący test, który okazał się dla mnie dość mylący i byłbym wdzięczny za wyjaśnienie. Oto test, który uruchomiłem bezpośrednio w powłoce R z terminala (nie używając RStudio lub 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

Czy możesz mi powiedzieć, gdzie jest mój błąd i co źle zrozumiałem w tym problemie?

użytkownik17911
źródło
1
Nie znam R, ale w innych językach masz wartości i typy referencyjne. Jeśli liczba całkowita jest typem wartości jak w C ++ lub C #, wówczas dowolne przypisanie utworzy nową liczbę całkowitą. Tak więc każda liczba całkowita będzie miała własny adres.
Hostel
1
Rzeczywiście, nawet obj_addr(x[1])dwukrotne uruchomienie powinno dać różne wyniki, ponieważ każda nowa liczba całkowita będzie miała swój własny adres.
Bas
@Bas Testowałem to, co wspomniałeś, to znaczy, działając kolejno obj_addr (x [1]), i rzeczywiście robiąc to, R zwraca za każdym razem inny wynik (inny adres pamięci). Ale nie rozumiem dlaczego, ponieważ, jak mi się wydaje, niczego nie przypisuję, więc nie tworzę nowego obiektu (dla którego oczywiście będzie nowy adres, ponieważ obiekty są niezmienne w R). Dla mnie obj_addr (x [1]) oznacza, że ​​właśnie czytam już istniejący obiekt.
user17911

Odpowiedzi:

5

Każdy obiekt R jest C (wywołany wskaźnikiem SEXP- na a) „multi-object” ( struct). Obejmuje to informacje (które R musi operować, np. lengthLiczbę referencji - aby wiedzieć, kiedy skopiować obiekt - i więcej) o obiekcie R, a także rzeczywiste dane obiektu R, do którego mamy dostęp.

lobstr::obj_addr, prawdopodobnie zwraca adres pamięci, na który SEXPwskazuje. Ta część pamięci zawiera zarówno informacje o obiekcie R, jak i dane. Ze środowiska R nie możemy / nie musimy uzyskiwać dostępu do (rzeczywistej) pamięci rzeczywistych danych w każdym obiekcie R.

Jak zauważa Adam w swojej odpowiedzi, funkcja [ kopiuje n-ty element danych zawartych w obiekcie C do nowego obiektu C i zwraca SEXPwskaźnik do R. Za każdym razem, gdy [jest wywoływany, nowy obiekt C jest tworzony i zwracany do R.

Nie możemy uzyskać dostępu do adresu pamięci każdego elementu rzeczywistych danych naszego obiektu przez R. Ale bawiąc się trochę, możemy prześledzić odpowiednie adresy za pomocą interfejsu API:

Funkcja uzyskiwania adresów:

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

A zastosowanie do naszych danych:

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

Kolejna różnica w pamięci między elementami danych naszego obiektu jest równa rozmiarowi inttypu:

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

Korzystanie z [funkcji:

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

To może być bardziej niż potrzebna obszerna odpowiedź i jest uproszczona w odniesieniu do faktycznych szczegółów technicznych, ale, mam nadzieję, oferuje wyraźniejszy „duży” obraz.

alexis_laz
źródło
Niesamowite! Naprawdę bardzo dziękuję za tak szczegółowe i jasne wyjaśnienie dla ludzi takich jak ja, którzy są całkowicie początkującymi w R. Ponadto, twój przykład jest bardzo imponujący, pokazując elastyczność języka R i jego możliwe potężne interakcje z innymi językami programowania. Wielkie dzięki za poświęcony czas i pomoc.
user17911
3

To jeden ze sposobów, aby na to spojrzeć. Jestem pewien, że jest bardziej techniczny pogląd. Pamiętaj, że w R prawie wszystko jest funkcją. Obejmuje funkcję wyciągu, [. Oto oświadczenie równoważne z x[1]:

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

Więc uruchamiasz funkcję, która zwraca wartość (sprawdź ?Extract). Ta wartość jest liczbą całkowitą. Podczas uruchamiania obj_addr(x[1])ocenia funkcję, x[1]a następnie obj_addr()zwraca jej zwrot, a nie adres pierwszego elementu tablicy, do którego przypisano jedno xi drugie y.

Adam
źródło
Dziękuję bardzo za pomoc i uwagę na mój problem. Rzeczywiście, tego nie wiedziałem, to znaczy, że odzyskanie wartości przez „Wyodrębnij” rzeczywiście tworzy nowy obiekt. Jak powiedziałem, jestem naprawdę początkującym w R! Dziękuję bardzo za poświęcony czas i opis.
user17911