2010-06-05 4 views
7

I this MSDN Magazine article, stany autorem (moje podkreślenie):Czy rozpakowywanie powoduje zwrócenie wskaźnika do wartości w pudełkowym obiekcie na stercie?

Note, że boks zawsze tworzy nowy obiekt i kopiuje bez opakowania wartość za bitów do obiektu. Z drugiej strony, unboxing po prostu zwraca wskaźnik do danych w pudełkowym obiekcie: nie ma kopii pamięci. Jednak zazwyczaj jest to , ponieważ twój kod będzie powodować, że dane wskazane przez unboxed odwołania , które mają być skopiowane w każdym razie.

Jestem zdezorientowany przez zdanie, które pogrubiłem i zdanie, które następuje po nim. Ze wszystkiego, co przeczytałem, w tym z this MSDN page, nigdy wcześniej nie słyszałem, że rozpakowanie po prostu zwraca wskaźnik do wartości na stercie. Miałem wrażenie, że rozpakowanie spowoduje, że będziesz mieć zmienną zawierającą kopię wartości na stosie, tak jak zaczynałeś. W końcu jeśli moja zmienna zawiera "wskaźnik do wartości na stercie", to nie mam typu wartości, mam wskaźnik.

Czy ktoś może wyjaśnić, co to oznacza? Czy autor był na crack? (W artykule jest co najmniej jeden inny rażący błąd). A jeśli to prawda, jakie są przypadki, w których "twój kod spowoduje, że dane wskazywane przez unboxed referencje będą mimo to skopiowane"?

Właśnie zauważyłem, że artykuł ma prawie 10 lat, więc może to jest coś, co zmieniło się bardzo wcześnie w życiu .Net.

Odpowiedz

6

Artykuł jest dokładny. Mówi jednak o tym, co naprawdę się dzieje, a nie jak wygląda IL, który generuje kompilator. W końcu program .NET nigdy nie wykonuje IL, wykonuje kod maszynowy wygenerowany z IL przez kompilator JIT.

I kod operacyjny unbox rzeczywiście generuje kod, który generuje wskaźnik do bitów na stercie, który reprezentuje wartość typu wartości. JIT generuje wywołanie do małej funkcji pomocnika w CLR o nazwie "JIT_Unbox". clr \ src \ vm \ jithelpers.cpp, jeśli masz kod źródłowy SSCLI20. Funkcja Object :: GetData() zwraca wskaźnik.

Stamtąd wartość najczęściej najpierw jest kopiowana do rejestru CPU. Które mogą być przechowywane gdzieś. Nie musi to być stos, może być członkiem obiektu typu odniesienia (sterty Gc). Lub zmienną statyczną (stertę ładowarki). Lub może być popchnięty na stos (wywołanie metody). Lub rejestr CPU może być użyty tak jak jest, gdy wartość jest używana w wyrażeniu.

Podczas debugowania kliknij prawym przyciskiem myszy okno edytora i wybierz "Przejdź do demontażu", aby zobaczyć kod maszyny.

+0

Rozumiem! Dziękuję Ci. –

1

Boks to akt rzucania instancji typu wartości do instancji typu odniesienia (albo object lub interfejsu), a typy referencji są przydzielane na stercie.

Zgodnie z "C# 4.0 w skrócie": "... unboxing kopiuje zawartość obiektu z powrotem do instancji typu wartościowego" i to oznacza na stosie.

W artykule ty odsyłającym autor:

public static void Main() { 

    Int32 v = 5; // Create an unboxed value type variable 
    Object o = v; // o refers to a boxed version of v 
    v = 123;  // Changes the unboxed value to 123 

    Console.WriteLine(v + ", " + (Int32) o); // Displays "123, 5" 
} 

Od tego kodu, można się domyślać, ile występują operacje bokserskie? Możesz być zaskoczony odkryciem, że odpowiedź jest trzecia! Przeanalizujmy ostrożnie kod , aby naprawdę zrozumieć, co się dzieje: . Najpierw tworzony jest typ wartości unblokowanej Int32 (v) i inicjowany do 5. Następnie tworzony jest typ odwołania do obiektu (o), który chce wskazywać na v. Jednak typy odniesienia zawsze muszą wskazywać na obiekty w stercie , więc C# wygenerował odpowiedni kod IL do pola v i zapisał adres wersji pudełkowej v w. Teraz 123 jest rozpakowywany , a dane referencyjne są kopiowane do w unboxed value type v; nie ma efektu na pudełkowej wersji v, więc wersja pudełkowa zachowuje wartość 5. Zauważ, że ten przykład pokazuje, w jaki sposób o jest rozpakowane (które zwraca wskaźnik do danych w o), , a następnie dane w o to pamięć skopiowana do niezakrytej wartości typ v.

+0

Tak, wszystko, co przeczytałem, mówi lub sugeruje, że dane są kopiowane z powrotem na stos. Jeśli chodzi o to wyjaśnienie, to jest ono sprzeczne: tak, a część cytowanego przez ciebie fragmentu pogrubionego jest właściwie tym, o czym mówiłem, kiedy powiedziałem, że w artykule jest błąd. Jest absolutnie nie w porządku - o nie jest rozpakowywany na v gdziekolwiek w tym przykładzie. –

5

Autor oryginalnego artykułu musiał odnieść się do tego, co dzieje się na poziomie IL. Istnieją dwa kody Unboxing: unbox i unbox.any.

Według MSDN regarding unbox.any:

Po nałożeniu na pudełkowej postaci typu wartości, unbox.any instrukcja wyodrębnia wartość zawartą w obj (typu O), a zatem odpowiednik unbox, po którym następuje ldobj.

i regarding unbox:

[...] unbox nie jest wymagane, aby skopiować typ wartości z obiektu. Zazwyczaj po prostu oblicza adres typu wartości, który jest już obecny wewnątrz obiektu pudełkowego .

Tak więc autor wiedział, o czym mówi.

Ten niewielki fakt o numerze unbox pozwala na pewne optymistyczne optymalizacje podczas pracy bezpośrednio z IL. Na przykład, jeśli masz pudełkowe int, które musisz przekazać do funkcji akceptującej ref int, możesz po prostu emitować kod operacyjny unbox, a odwołanie do int będzie gotowe do użycia w stosie. W takim przypadku funkcja zmieni rzeczywistą zawartość obiektu boksu, co jest zupełnie niemożliwe na poziomie C#. Oszczędza to przed koniecznością przydzielenia miejsca dla tymczasowej zmiennej lokalnej, rozpakowania int tam, przekazania ref do int funkcji, a następnie utworzenia nowego obiektu boksu, aby ponownie wpisać int, odrzucając stare pole.

Oczywiście, gdy pracujesz na poziomie C#, nie możesz wykonywać żadnych takich optymalizacji, więc zwykle dzieje się tak, że kod wygenerowany przez kompilator prawie zawsze będzie kopiował zmienną z obiektu pudełkowego przed dalsze korzystanie z niego.

+0

+1 Świetny dodatek, dziękuję. –

Powiązane problemy