C++
Układ typów obiektów
Szukaj…
Uwagi
Zobacz także Rozmiar typów całkowych .
Rodzaje klas
Przez „klasę” rozumiemy typ, który został zdefiniowany przy użyciu słowa kluczowego class
lub struct
(ale nie enum struct
ani enum class
).
Nawet pusta klasa nadal zajmuje co najmniej jeden bajt pamięci; będzie zatem składać się wyłącznie z wyściółki. Zapewnia to, że jeśli
p
wskazuje na obiekt pustej klasy, top + 1
jest odrębnym adresem i wskazuje na odrębny obiekt. Możliwe jest jednak, że pusta klasa ma rozmiar 0, gdy zostanie użyta jako klasa podstawowa. Zobacz optymalizację pustej bazy .class Empty_1 {}; // sizeof(Empty_1) == 1 class Empty_2 {}; // sizeof(Empty_2) == 1 class Derived : Empty_1 {}; // sizeof(Derived) == 1 class DoubleDerived : Empty_1, Empty_2 {}; // sizeof(DoubleDerived) == 1 class Holder { Empty_1 e; }; // sizeof(Holder) == 1 class DoubleHolder { Empty_1 e1; Empty_2 e2; }; // sizeof(DoubleHolder) == 2 class DerivedHolder : Empty_1 { Empty_1 e; }; // sizeof(DerivedHolder) == 2
Obiektowa reprezentacja typu klasy zawiera reprezentacje obiektowe klasy podstawowej i niestatycznych typów członków. Dlatego na przykład w następującej klasie:
struct S { int x; char* y; };
istnieje kolejna sekwencja bajtów
sizeof(int)
w obiekcieS
, zwana podobiektem, która zawiera wartośćx
, oraz kolejny podobiekt osizeof(char*)
który zawiera wartośćy
. Tych dwóch nie można przeplatać.Jeśli typ klasy ma elementy i / lub klasy podstawowe z typami
t1, t2,...tN
, rozmiar musi wynosić co najmniejsizeof(t1) + sizeof(t2) + ... + sizeof(tN)
biorąc pod uwagę poprzednie punkty . Jednak w zależności od wymagań dotyczących wyrównania elementów i klas bazowych kompilator może zostać zmuszony do wstawienia wypełnienia między podobiektami lub na początku lub na końcu całego obiektu.struct AnInt { int i; }; // sizeof(AnInt) == sizeof(int) // Assuming a typical 32- or 64-bit system, sizeof(AnInt) == 4 (4). struct TwoInts { int i, j; }; // sizeof(TwoInts) >= 2 * sizeof(int) // Assuming a typical 32- or 64-bit system, sizeof(TwoInts) == 8 (4 + 4). struct IntAndChar { int i; char c; }; // sizeof(IntAndChar) >= sizeof(int) + sizeof(char) // Assuming a typical 32- or 64-bit system, sizeof(IntAndChar) == 8 (4 + 1 + padding). struct AnIntDerived : AnInt { long long l; }; // sizeof(AnIntDerived) >= sizeof(AnInt) + sizeof(long long) // Assuming a typical 32- or 64-bit system, sizeof(AnIntDerived) == 16 (4 + padding + 8).
Jeśli dopełnienie zostanie wstawione do obiektu ze względu na wymagania dotyczące wyrównania, rozmiar będzie większy niż suma rozmiarów elementów i klas podstawowych. Przy wyrównaniu
n
bajtów rozmiar będzie zwykle najmniejszą wielokrotnościąn
która jest większa niż rozmiar wszystkich elementów i klas podstawowych. Każdy członekmemN
będzie zazwyczaj umieszczony pod adresem będącym wielokrotnościąalignof(memN)
, an
będzie zazwyczaj największymalignof
spośród wszystkichalignof
członków. Z tego powodu, jeśli po elemencie o mniejszymalignof
następuje element o większymalignof
, istnieje możliwość, że ten drugi element nie zostanie właściwie wyrównany, jeśli zostanie umieszczony bezpośrednio za pierwszym. W takim przypadku wyściółka (znana również jako element wyrównujący ) zostanie umieszczona między dwoma elementami, tak że ten ostatni element może mieć pożądane ustawienie. I odwrotnie, jeśli po elemencie o większymalignof
następuje element o mniejszymalignof
, zwykle nie będzie konieczne wypełnianie. Ten proces jest również znany jako „pakowanie”.
Ze względu na to, że klasy zwykle dzieląalignof
swojego członka z największymalignof
, klasy będą zazwyczaj wyrównane zalignof
największego wbudowanego typu, który zawierają bezpośrednio lub pośrednio.// Assume sizeof(short) == 2, sizeof(int) == 4, and sizeof(long long) == 8. // Assume 4-byte alignment is specified to the compiler. struct Char { char c; }; // sizeof(Char) == 1 (sizeof(char)) struct Int { int i; }; // sizeof(Int) == 4 (sizeof(int)) struct CharInt { char c; int i; }; // sizeof(CharInt) == 8 (1 (char) + 3 (padding) + 4 (int)) struct ShortIntCharInt { short s; int i; char c; int j; }; // sizeof(ShortIntCharInt) == 16 (2 (short) + 2 (padding) + 4 (int) + 1 (char) + // 3 (padding) + 4 (int)) struct ShortIntCharCharInt { short s; int i; char c; char d; int j; }; // sizeof(ShortIntCharCharInt) == 16 (2 (short) + 2 (padding) + 4 (int) + 1 (char) + // 1 (char) + 2 (padding) + 4 (int)) struct ShortCharShortInt { short s; char c; short t; int i; }; // sizeof(ShortCharShortInt) == 12 (2 (short) + 1 (char) + 1 (padding) + 2 (short) + // 2 (padding) + 4 (int)) struct IntLLInt { int i; long long l; int j; }; // sizeof(IntLLInt) == 16 (4 (int) + 8 (long long) + 4 (int)) // If packing isn't explicitly specified, most compilers will pack this as // 8-byte alignment, such that: // sizeof(IntLLInt) == 24 (4 (int) + 4 (padding) + 8 (long long) + // 4 (int) + 4 (padding)) // Assume sizeof(bool) == 1, sizeof(ShortIntCharInt) == 16, and sizeof(IntLLInt) == 24. // Assume default alignment: alignof(ShortIntCharInt) == 4, alignof(IntLLInt) == 8. struct ShortChar3ArrShortInt { short s; char c3[3]; short t; int i; }; // ShortChar3ArrShortInt has 4-byte alignment: alignof(int) >= alignof(char) && // alignof(int) >= alignof(short) // sizeof(ShortChar3ArrShortInt) == 12 (2 (short) + 3 (char[3]) + 1 (padding) + // 2 (short) + 4 (int)) // Note that t is placed at alignment of 2, not 4. alignof(short) == 2. struct Large_1 { ShortIntCharInt sici; bool b; ShortIntCharInt tjdj; }; // Large_1 has 4-byte alignment. // alignof(ShortIntCharInt) == alignof(int) == 4 // alignof(b) == 1 // Therefore, alignof(Large_1) == 4. // sizeof(Large_1) == 36 (16 (ShortIntCharInt) + 1 (bool) + 3 (padding) + // 16 (ShortIntCharInt)) struct Large_2 { IntLLInt illi; float f; IntLLInt jmmj; }; // Large_2 has 8-byte alignment. // alignof(IntLLInt) == alignof(long long) == 8 // alignof(float) == 4 // Therefore, alignof(Large_2) == 8. // sizeof(Large_2) == 56 (24 (IntLLInt) + 4 (float) + 4 (padding) + 24 (IntLLInt))
Jeśli ścisłe wyrównanie zostanie wymuszone za pomocą
alignas
, zostanie zastosowane wypełnienie, aby wymusić na typie osiągnięcie określonego wyrównania, nawet jeśli w innym przypadku byłby mniejszy. Na przykład, z poniższą definicją, wChars<5>
na końcu zostaną wstawione trzy (lub więcej) bajty wypełniające, tak że ich całkowity rozmiar to 8. Nie jest możliwe, aby klasa z wyrównaniem 4 miała rozmiar z 5, ponieważ nie byłoby możliwe utworzenie tablicy tej klasy, więc rozmiar należy „zaokrąglić w górę” do wielokrotności 4, wstawiając bajty dopełniania.// This type shall always be aligned to a multiple of 4. Padding shall be inserted as // needed. // Chars<1>..Chars<4> are 4 bytes, Chars<5>..Chars<8> are 8 bytes, etc. template<size_t SZ> struct alignas(4) Chars { char arr[SZ]; }; static_assert(sizeof(Chars<1>) == sizeof(Chars<4>), "Alignment is strict.\n");
- Jeśli dwa niestatyczne elementy klasy mają ten sam specyfikator dostępu , to ten, który pojawia się później w kolejności deklaracji, gwarantuje, że pojawi się później w reprezentacji obiektu. Ale jeśli dwa elementy niestatyczne mają różne specyfikatory dostępu, ich względna kolejność w obiekcie jest nieokreślona.
- Nie jest określone, w jakiej kolejności podobiekty klasy podstawowej pojawiają się w obiekcie, czy występują kolejno i czy pojawiają się przed, po, czy pomiędzy podobiektami składowymi.
Typy arytmetyczne
Wąskie typy znaków
Typ unsigned char
używa wszystkich bitów do reprezentowania liczby binarnej. Dlatego na przykład, jeśli unsigned char
ma długość 8 bitów, wówczas 256 możliwych wzorców bitowych obiektu char
reprezentuje 256 różnych wartości {0, 1, ..., 255}. Gwarantowana liczba 42 jest reprezentowana przez wzór bitowy 00101010
.
signed char
typ signed char
nie ma bitów wypełniających, tzn. Jeśli signed char
ma długość 8 bitów, wówczas ma 8 bitów pojemności do reprezentowania liczby.
Należy pamiętać, że te gwarancje nie dotyczą typów innych niż wąskie typy znaków.
Typy całkowite
Typy całkowite bez znaku używają czystego systemu binarnego, ale mogą zawierać bity wypełniające. Na przykład możliwe jest (choć mało prawdopodobne), że liczba całkowita unsigned int
ma długość 64 bitów, ale może przechowywać tylko liczby całkowite od 0 do 2 32-1 włącznie. Pozostałe 32 bity to bity wypełniające, do których nie należy pisać bezpośrednio.
Podpisane typy liczb całkowitych używają systemu binarnego z bitem znaku i ewentualnie bitami wypełniającymi. Wartości należące do wspólnego zakresu typu liczby całkowitej ze znakiem i odpowiadającego typu liczby całkowitej bez znaku mają tę samą reprezentację. Na przykład, jeśli wzór bitowy 0001010010101011
unsigned short
obiektu reprezentuje wartość 5291
, wówczas reprezentuje również wartość 5291
gdy jest interpretowany jako short
obiekt.
Definiuje się implementację, czy używana jest reprezentacja uzupełnienia do dwóch, uzupełnienie do jednego lub wielkość znaku, ponieważ wszystkie trzy systemy spełniają wymaganie z poprzedniego akapitu.
Typy zmiennoprzecinkowe
Reprezentacja wartości typów zmiennoprzecinkowych jest zdefiniowana w implementacji. Najczęściej typy float
i double
są zgodne z IEEE 754 i mają długość 32 i 64 bity (więc na przykład float
miałaby 23 bity precyzji, która następowałaby po 8 bitach wykładnikowych i 1 bicie znaku). Jednak standard niczego nie gwarantuje. Typy zmiennoprzecinkowe często mają „reprezentacje pułapek”, które powodują błędy, gdy są używane w obliczeniach.
Tablice
Typ tablicy nie ma wypełnienia między elementami. Dlatego tablica z typem elementu T
jest po prostu sekwencją obiektów T
ułożonych w pamięci, w kolejności.
Tablica wielowymiarowa jest tablicą tablic, a powyższe stosuje się rekurencyjnie. Na przykład, jeśli mamy deklarację
int a[5][3];
to a
jest tablicą 5 tablic po 3 int
. Dlatego a[0]
, który składa się z trzech elementów: a[0][0]
, a[0][1]
, a[0][2]
, jest umieszczony w pamięci przed a[1]
, który składa się a[1][0]
, a[1][1]
i a[1][2]
. Nazywa się to porządkiem głównym rzędu.