2013-07-07 22 views
6

Dlaczego:Int16 - pojemność bajtów in.net?

short a=0; 
Console.Write(Marshal.SizeOf(a)); 

pokazuje 2

Ale jeśli widzę kodu IL widzę:

/*1*/ IL_0000: ldc.i4.0  
/*2*/ IL_0001: stloc.0  
/*3*/ IL_0002: ldloc.0  
/*4*/ IL_0003: box   System.Int16 
/*5*/ IL_0008: call  System.Runtime.InteropServices.Marshal.SizeOf 
/*6*/ IL_000D: call  System.Console.Write 

LDC na linii # 1 wskazuje:

push 0 na stos jako int32.

Musiało więc zająć 4 bajtów.

Ale sizeOf pokazuje 2 bajtów ...

Co ja tu brakuje? ile bajtów zajmuje skrót mem?

Słyszałem o sytuacjach, w których istnieje dopełnienie do 4 bajtów, więc byłoby szybciej się z nim uporać. czy to też jest tutaj?

(proszę ignorować SyncRoot i GC korzeń flagę bajt Po prostu pytam o 2 vs 4)

+4

Rozmiar w pamięci jest tylko znaczącą koncepcją po umieszczeniu jej w większej strukturze, na przykład w tablicy. Kiedy masz zmienną lokalną, zwykle zajmuje ona pełny rejestr (64 bity na AMD64), nawet jeśli jest to tylko jeden bajt. Kompilator C# używa Int32 wewnętrznie dla większości rzeczy, co jest w jego prawach tak długo, jak obserwowane zachowanie pasuje do tego z Int16. – CodesInChaos

+0

@CodesInChaos Więc zajmuje 4 bajty w 32-bitowym? a jeśli tak, dlaczego sieOf pokazuje 2? –

+1

Rozmiar wynosi 2, ponieważ jest zdefiniowany w ten sposób, a 2 bajty są wystarczające. Ale jeśli masz zmienną lokalną, zajmuje ona tyle bajtów, ile potrzebuje kompilator/JITer. Dopóki nie wpłynie to na działanie programu, mogą robić to, co im się podoba. Często pojedyncza zmienna lokalna jest przechowywana w różnych miejscach podczas uruchamiania metody. W twoim przypadku mogą nawet całkowicie wyeliminować 'a' i po prostu użyć stałego' 2'. – CodesInChaos

Odpowiedz

5

Łatwo jest powiedzieć, co się dzieje, patrząc na available LDC instructions. Zwróć uwagę na ograniczony zestaw dostępnych typów argumentów, dostępna jest wersja no, która ładuje stałą typu short. Po prostu int, long, float i double. Ograniczenia te są widoczne w innym miejscu, na przykład instrukcja Opcodes.Add jest podobnie ograniczona, brak obsługi dodawania zmiennych jednego z mniejszych typów.

Zestaw instrukcji IL został zaprojektowany bardzo celowo w ten sposób, odzwierciedla możliwości prostego procesora 32-bitowego. Rodzaj procesora, o którym można pomyśleć, to rodzaj RISC, mieli dzień siana w dziewiętnastu latach. Wiele 32-bitowych rejestrów cpu, które mogą manipulować tylko 32-bitowymi liczbami całkowitymi i typami zmiennoprzecinkowymi IEEE-754. Rdzeń Intel x86 nie jest dobrym przykładem, a bardzo często jest to projekt CISC, który faktycznie obsługuje ładowanie i robienie arytmetyki na operandach 8-bitowych i 16-bitowych. Ale to bardziej historyczny przypadek, dzięki któremu łatwe tłumaczenie mechaniczne programów rozpoczęło się na 8-bitowych 8080 i 16-bitowych procesorach 8086. Ale taka możliwość nie przychodzi za darmo, manipulowanie 16-bitowymi wartościami faktycznie kosztuje dodatkowy cykl procesora.

Dopasowanie IL do 32-bitowych możliwości procesora znacznie ułatwia zadanie facetowi wdrażającemu jitter. Miejsca w magazynie mogą nadal być mniejsze, ale tylko obciążenia, sklepy i konwersje muszą być obsługiwane. I tylko wtedy, gdy jest to potrzebne, twoja zmienna "a" jest zmienną lokalną, która w każdym razie zajmuje 32 bity na ramce stosu lub rejestrze cpu.Tylko magazyny do pamięci muszą być przycięte do odpowiedniego rozmiaru.

W fragmencie kodu nie ma żadnych niejednoznaczności. Wartość zmiennej musi zostać podzielona, ​​ponieważ Marshal.SizeOf() przyjmuje argument typu obiekt. Wartość pudełkowa identyfikuje typ wartości przez uchwyt typu, wskaże System.Int16. Marshal.SizeOf() ma wbudowaną wiedzę, która wie, że zajmuje 2 bajty.

Te ograniczenia odzwierciedlają język C# i powodują niespójność. Ten rodzaj błędu kompilacji zawsze befuddles i irytuje programistów C#:

byte b1 = 127; 
    b1 += 1;   // no error 
    b1 = b1 + 1;  // error CS0266 

co wynika z ograniczeń IL, nie ma operator dodać, że trwa argumentów bajtów. Muszą one zostać przekonwertowane na następny większy zgodny typ: int w tym przypadku. Tak działa na 32-bitowym procesorze RISC. Teraz jest problem, 32-bitowy wynik musi zostać wbity z powrotem w zmienną, która może przechowywać tylko 8-bitów. Język C# stosuje ten sam młotek w pierwszym zadaniu, ale nielogicznie wymaga odlewania w drugim zadaniu.

+0

Hans, _ "int, long, float and double" _ - co z jedynym typem, który może reprezentować czystą dokładną liczbę dziesiętną ("dziesiętną")? jak tu pasuje? dzięki. (p. Uwielbiam strukturę odpowiedzi: historia, teraźniejszość, przykład.) –

+0

CLR nie ma specjalnej wiedzy o System.Decimal, tylko kompilator robi. Dla CLR jest to zwykły typ struktury. –

7

Specyfikacja CLI jest bardzo wyraźny o rodzajach danych, które są dopuszczone do na stosie. Krótka 16-bitowa liczba całkowita nie jest jedną z nich, więc takie typy liczb całkowitych są konwertowane na 32-bitowe liczby całkowite (4 bajty), gdy są one ładowane na stos.

Partition III.1.1 zawiera wszystkie szczegóły:

1.1 Typy danych

Podczas CTS definiuje bogaty system typów i CLS określa podzbiór, który może być używany jako język interoperacyjność, sam CLI zajmuje się znacznie prostszym zestawem typów. Te typy zawierają zdefiniowane przez użytkownika wartości typy i podzbiór wbudowanych typów. Podzbiór, znaną jako „podstawowe typy CLI” zawiera następujące typy:

  • Podzbiór rodzajów pełnych, numerycznych (int32int64, native int i F).
  • Odwołania do obiektów (O) bez rozróżnienia między typem obiektu, do którego istnieje odwołanie. Typy wskaźników (native unsigned int i &) bez rozróżnienia typu wskazanego.

Należy pamiętać, że odniesieniom do obiektów i typom wskaźników można przypisać wartość null. Jest to zdefiniowane w całym CLI jako zero (wzór bitowy wszystkich bitów-zero).

1.1.1 numeryczne typy danych

  • CLI działa tylko od rodzaju numerycznych int32 (4-bajtowe liczbą całkowitą ze znakiem), int64 (8-bajtowe liczbą całkowitą ze znakiem), native int (liczby całkowite natywnej wielkości), a F (natywne liczby zmiennoprzecinkowe ). Jednak zestaw instrukcji CIL pozwala na implementację dodatkowych typów danych:

  • Krótkie liczby całkowite: stos oceny zawiera tylko 4 lub 8-bajtowe liczby całkowite, ale inne lokalizacje (argumenty, zmienne lokalne, statyka, elementy tablicy, pola) mogą zawierać liczby całkowite z przedziału 1- lub 2-bajtowego. Dla celów operacji stos rodzaje bool i karbonizatu są traktowane jako unsigned 1 bajtu i oznaczają liczby całkowite 2 bajtów, odpowiednio. Ładowania z tych miejsc na stosu konwertuje je do wartości 4-bajtowe:

    • zera rozciągające typów unsigned int8 unsigned INT16, logicznych, i karbonizatu;
    • znak rozciągające typów int8 i INT16;
    • zerowej rozciąga się na niepodpisanych pośrednich i elementów ładunku (ldind.u*, ldelem.u*, etc.) ;; i
    • logowania rozciąga się na podpisanych pośrednich i elementów ładunku (ldind.i*, ldelem.i*, etc.)

Przechowywanie do liczb całkowitych, logicznych i znaków (stloc, stfld, stind.i1, stelem.i2, itd.) obcina. Użyj instrukcji conv.ovf.*, aby wykryć, kiedy to obcięcie skutkuje wartością, która nieprawidłowo reprezentuje oryginalną wartość.

[Uwaga: Liczby całkowite (tj. 1- i 2-bajtowe) są ładowane jako liczby 4-bajtowe na wszystkich architekturach, a te 4-bajtowe liczby są zawsze śledzone jako oddzielne od 8-bajtowych liczb. Pomaga to przenośność kodu poprzez zapewnienie, że domyślne zachowanie arytmetyczne (czyli gdy nie ma instrukcji conv lub conv.ovf jest wykonywany) będą miały identyczne wyniki na wszystkich implementacjach.] Instrukcje

przekonwertować dają krótkie wartości całkowitych faktycznie zostawić int32 (32-bitowa) wartość na stosie, ale gwarantuje się, że tylko małe bity mają znaczenie (tj. Bardziej znaczące bity są zerowe dla niepodpisanych konwersji lub rozszerzenie znaków dla podpisanych konwersji). Aby poprawnie symulować pełny zestaw krótkich operacji całkowitą konwersję do krótkiego całkowitej jest wymagane przed instrukcją div, rem, shr, porównanie i warunkowych branży.

& hellip; i tak dalej.

Mówiąc spekulacyjnie, decyzja ta została prawdopodobnie wykonana zarówno dla uproszczenia architektury lub dla prędkości (ewentualnie jedno i drugie). Nowoczesne procesory 32-bitowe lub 64-bitowe może działać bardziej skutecznie z 32-bitowymi liczbami całkowitymi, niż przy użyciu 16-bitowe liczby całkowite i od każdej liczby całkowitej, która może być reprezentowana w 2 bajty mogą być również reprezentowane w 4 bajtów, to zachowanie jest uzasadnione .

Jednorazowy użytek z 2-bajtową liczbą całkowitą, w przeciwieństwie do 4-bajtowej, ma większe znaczenie, jeśli chodzi o wykorzystanie pamięci, niż przy szybkości/wydajności wykonania. W takim przypadku trzeba mieć całą masę tych wartości, prawdopodobnie zapakowanych w strukturę. I właśnie wtedy zależy Ci na wyniku Marshal.SizeOf.

1

Specyfikacja języka C# określa sposób zachowania programu. Nie mówi, jak to zaimplementować, o ile zachowanie jest prawidłowe. Jeśli zapytasz o rozmiar short, zawsze otrzymujesz 2.

W praktyce C# kompiluje się do CIL, gdzie typy całkowe mniejsze niż 32 bity są reprezentowane jako 32-bitowe liczby całkowite na stosie .

Następnie JITer ponownie zmienia to, co jest odpowiednie dla docelowego sprzętu, zazwyczaj jest to część pamięci na stosie lub w rejestrze.

Dopóki żadna z tych zmian nie ulegnie zmianie obserwowalne zachowanie są one legalne.

W praktyce rozmiar zmiennych lokalnych jest w dużej mierze nieistotny, liczy się rozmiar tablic. Tablica miliona short s zwykle zajmuje 2 MB.


jest wirtualny stos IL działa dalej, które różni się od stosu operacje kod urządzenie.

1

CLR działa natywnie tylko na 32-bitowych i 64-bitowych liczbach całkowitych na stosie. Odpowiedź leży w tej instrukcji:

box System.Int16 

Oznacza to, że typ wartości jest oznaczony jako Int16. Kompilator C# automatycznie wysyła to pole do wywołania Marshal.SizeOf (obiekt), który z kolei wywołuje metodę GetType() na pudełkowej wartości, która zwraca typeof (System.Int16).