4.3 — Rozmiary obiektów i rozmiar operatora

Rozmiary obiektów

Jak nauczyłeś się na lekcji 4.1 -- Wprowadzenie do podstawowych typów danych, pamięć nowoczesnych maszyn jest zazwyczaj zorganizowana w jednostki wielkości bajtów, przy czym każdy bajt pamięci ma unikalny adres. Do tego momentu przydatne było myślenie o pamięci jako o zbiorze schowków lub skrzynek pocztowych, w których możemy umieszczać i pobierać informacje, a o zmiennych jako o nazwach umożliwiających dostęp do tych schowków lub skrzynek pocztowych.

Jednak ta analogia nie jest do końca poprawna pod jednym względem - większość obiektów w rzeczywistości zajmuje więcej niż 1 bajt pamięci. Pojedynczy obiekt może używać 1, 2, 4, 8 lub nawet większej liczby kolejnych adresów pamięci. Ilość pamięci wykorzystywanej przez obiekt zależy od jego typu danych.

Ponieważ dostęp do pamięci zazwyczaj uzyskujemy poprzez nazwy zmiennych (a nie bezpośrednio poprzez adresy pamięci), kompilator jest w stanie ukryć przed nami szczegółowe informacje o tym, ile bajtów wykorzystuje dany obiekt. Kiedy uzyskujemy dostęp do jakiejś zmiennej x w naszym kodzie źródłowym, kompilator wie, ile bajtów danych należy pobrać (w oparciu o typ zmiennej x) i wygeneruje odpowiedni kod języka maszynowego, aby obsłużyć te szczegóły za nas.

Mimo to istnieje kilka powodów, dla których warto wiedzieć, ile pamięci wykorzystuje obiekt.

Po pierwsze, im więcej pamięci wykorzystuje obiekt, tym więcej informacji może uzyskać wstrzymaj.

Pojedynczy bit może pomieścić 2 możliwe wartości, 0 lub 1:

bit 0
0
1

2 bity mogą pomieścić 4 możliwe wartości:

bit 0bit 1
00
01
10
11

3 bity mogą pomieścić 8 możliwych wartości:

bit 0bit 1bit 2
000
001
010
011
100
101
110
111

Uogólniając, obiekt z n bitami (gdzie n jest liczbą całkowitą) może przechowywać 2n (2 do potęgi n, również powszechnie zapisywanej jako 2^n) unikalnych wartości. Dlatego przy 8-bitowym bajcie obiekt wielkości bajtu może przechowywać 28 (256) różnych wartości. Obiekt korzystający z 2 bajtów może przechowywać 2^16 (65536) różnych wartości!

W związku z tym rozmiar obiektu ogranicza ilość unikalnych wartości, które może przechowywać — obiekty wykorzystujące więcej bajtów mogą przechowywać większą liczbę unikalnych wartości. Zbadamy to bliżej, gdy będziemy mówić więcej o liczbach całkowitych.

Po drugie, komputery mają skończoną ilość wolnej pamięci. Za każdym razem, gdy definiujemy obiekt, niewielka część tej wolnej pamięci jest używana tak długo, jak obiekt istnieje. Ponieważ nowoczesne komputery mają dużo pamięci, wpływ ten jest zwykle znikomy. Jednakże w przypadku programów, które wymagają dużej ilości obiektów lub danych (np. gra renderująca miliony wielokątów) różnica pomiędzy wykorzystaniem obiektów 1-bajtowych i 8-bajtowych może być znacząca.

Kluczowa informacja

Nowi programiści często zbytnio skupiają się na optymalizacji swojego kodu, aby zużywać jak najmniej pamięci. W większości przypadków robi to pomijalną różnicę. Skoncentruj się na pisaniu łatwego w utrzymaniu kodu i optymalizuj tylko wtedy i tam, gdzie korzyści będą znaczące.

Podstawowe rozmiary typów danych

Kolejnym oczywistym pytaniem jest „ile pamięci zużywają obiekty danego typu danych?”. Być może zaskakujące jest to, że standard C++ nie definiuje dokładnego rozmiaru (w bitach) żadnego z typów podstawowych.

Zamiast tego standard mówi, co następuje:

  • Obiekt musi zajmować co najmniej 1 bajt (aby każdy obiekt miał odrębny adres pamięci).
  • Bajt musi mieć co najmniej 8 bitów.
  • Typy całkowe char, short, int, long, I long long mają minimalny rozmiar wynosi odpowiednio 8, 16, 16, 32 i 64 bity.
  • char i char8_t to dokładnie 1 bajt (co najmniej 8 bitów).

Nomenklatura

Kiedy mówimy o rozmiarze typu, tak naprawdę mamy na myśli rozmiar tworzonego obiektu tego typu.

W tej serii tutoriali przedstawimy uproszczony widok, przyjmując pewne rozsądne założenia, które są ogólnie prawdziwe dla współczesnych architektury:

  • Bajt to 8 bitów.
  • Pamięć jest adresowalna bajtowo (możemy uzyskać dostęp do każdego bajtu pamięci niezależnie).
  • Obsługa zmiennoprzecinkowa jest zgodna z IEEE-754.
  • Jesteśmy w architekturze 32-bitowej lub 64-bitowej.

Biorąc pod uwagę powyższe założenia, możemy rozsądnie stwierdzić, co następuje:

KategoriaTypMinimum RozmiarTypowy rozmiar
Booleanbool1 bajt1 bajt
Znakchar1 bajt (dokładnie)1 bajt
wchar_t1 bajt2 lub 4 bajty
char8_t1 bajt1 bajt
char16_t2 bajty2 bajty
char32_t4 bajty4 bajty
Całkakrótki2 bajty2 bajty
int2 bajty4 bajty
long4 bajty4 lub 8 bajtów
long long8 bajtów8 bajtów
Zmiennoprzecinkowefloat4 bajty4 bajty
double8 bajtów8 bajtów
long double8 bajtów8, 12 lub 16 bajty
Wskaźnikstd::nullptr_t4 bajty4 lub 8 bajtów

Wskazówka

Aby zapewnić maksymalną przenośność, nie powinieneś zakładać, że obiekty są większe niż określony rozmiar minimalny.

Alternatywnie, jeśli chcesz założyć, że typ ma rozmiar inny niż minimalny (np. że int ma wartość najmniej 4 bajtów), możesz użyć static_assert , aby kompilator nie wykonał kompilacji, jeśli jest skompilowany na architekturze, w której to założenie nie jest prawdziwe. Jak to zrobić na lekcji 9.6 — Assert i static_assert.

Powiązana treść

Możesz znaleźć więcej informacji o tym, co standard C++ mówi o minimalnym rozmiarze różnych typów tutaj.

Operator sizeof

Aby określić rozmiar typów danych na konkretnej maszynie, C++ udostępnia operator o nazwie sizeof. Słowo kluczowe sizeofoperator jest operatorem jednoargumentowym, który przyjmuje typ lub zmienną i zwraca rozmiar obiektu tego typu (w bajtach). Możesz skompilować i uruchomić następujący program, aby sprawdzić, jak duże są niektóre typy danych:

#include <iomanip> // for std::setw (which sets the width of the subsequent output)
#include <iostream>
#include <climits> // for CHAR_BIT

int main()
{
    std::cout << "A byte is " << CHAR_BIT << " bits\n\n";

    std::cout << std::left; // left justify output

    std::cout << std::setw(16) << "bool:" << sizeof(bool) << " bytes\n";
    std::cout << std::setw(16) << "char:" << sizeof(char) << " bytes\n";
    std::cout << std::setw(16) << "short:" << sizeof(short) << " bytes\n";
    std::cout << std::setw(16) << "int:" << sizeof(int) << " bytes\n";
    std::cout << std::setw(16) << "long:" << sizeof(long) << " bytes\n";
    std::cout << std::setw(16) << "long long:" << sizeof(long long) << " bytes\n";
    std::cout << std::setw(16) << "float:" << sizeof(float) << " bytes\n";
    std::cout << std::setw(16) << "double:" << sizeof(double) << " bytes\n";
    std::cout << std::setw(16) << "long double:" << sizeof(long double) << " bytes\n";

    return 0;
}

Oto dane wyjściowe z komputera autora:

bool:           1 bytes
char:           1 bytes
short:          2 bytes
int:            4 bytes
long:           4 bytes
long long:      8 bytes
float:          4 bytes
double:         8 bytes
long double:    8 bytes

Twoje wyniki mogą się różnić w zależności od kompilatora, architektury komputera, systemu operacyjnego, ustawień kompilacji (32-bitowa vs 64-bitowa) itp...

Próba użycia sizeof na niekompletnym typie (takim as void) spowoduje błąd kompilacji.

Dla użytkowników gcc

Jeśli nie wyłączyłeś rozszerzeń kompilatora, gcc pozwala sizeof(void) zwrócić 1 zamiast generować diagnostykę (Pointer-Arith). Jak wyłączyć rozszerzenia kompilatora pokazujemy w lekcji 0.10 -- Konfigurowanie kompilatora: Rozszerzenia kompilatora.

Możesz także użyć operatora sizeof na nazwie zmiennej:

#include <iostream>

int main()
{
    int x{};
    std::cout << "x is " << sizeof(x) << " bytes\n";

    return 0;
}
x is 4 bytes

Dla zaawansowanych czytelników

sizeof nie obejmuje dynamicznie alokowanej pamięci używanej przez obiekt. Dynamiczną alokację pamięci omówimy w przyszłej lekcji.

Podstawowa wydajność typów danych

Na nowoczesnych maszynach obiekty podstawowych typów danych są szybkie, więc wydajność podczas używania lub kopiowania tych typów zasadniczo nie powinna stanowić problemu.

Na marginesie…

Można założyć, że typy zużywające mniej pamięci będą szybsze niż typy wykorzystujące więcej pamięci. Nie zawsze jest to prawdą. Procesory są często optymalizowane do przetwarzania danych o określonym rozmiarze (np. 32 bity), a typy pasujące do tego rozmiaru mogą być przetwarzane szybciej. Na takiej maszynie 32-bitowy int może być szybszy niż 16-bitowy krótki lub 8-bitowy znak.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
462 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze