Blog

C++, Regular

Ile waży… wskaźnik?

17 Wrz 2018

Wiadomo. Wskaźnik waży 4 bajty na architekturze 32-bitowej, a 8 bajtów na architekturze 64-bitowej. Ale czy to jedyne możliwości? Jaka może być wielkość wskaźnika?

Żeby zastanawiać się nad tym ile waży wskaźnik, może najpierw zastanówmy się czym jest wskaźnik. I jaką wartość reprezentuje.

Fizyka wskaźnika

W zależności od tego, czy nasz program działa bezpośrednio na sprzęcie (mikrokontrolery z małymi systemami czasu rzeczywistego), czy na większej maszynie, jak na przykład domowy PC z systemem Linux fizycznie wskaźnik będzie oznaczał coś innego. W przypadku małych mikrokontrolerów wskaźnik będzie najczęściej po prostu adresem w pamięci, pod którym faktycznie znajdują się dane. W drugim przypadku będzie to adres w pamięci zmapowanej przez MMU dla konkretnego procesu uruchomionego w systemie operacyjnym.

W systemach z bezpośrednim dostępem do pamięci jego wielkość odpowiada ilości bitów wykorzystanych do zaadresowania fizycznej pamięci. W systemach 32-bitowych są to 4 bajty, które pozwalają zaadresować 232, czyli 4294967296 bajtów. To jest 4096 MB, czyli 4GB pamięci.
W przypadku większych maszyn jego wielkość odpowiada wielkości pamięci jaką maksymalnie może zaadresować jeden proces pracujący w systemie. Z tego właśnie powodu w starszych komputerach nie mogliśmy przydzielić procesowi więcej niż 4GB pamięci. Proces po prostu nie był w stanie jej zaadresować.
Systemy 64-bitowe znoszą to ograniczenie. A może tylko je po raz kolejny odraczają?

Przejdźmy jednak do przykładu i wykonajmy poniższy kod:

int main() {
    std::cout
        << sizeof(char) << " sizeof(char)" << "\n"
        << sizeof(char*) << " sizeof(char*)" << "\n"
        << sizeof(int) << " sizeof(int)" << "\n"
        << sizeof(int*) << " sizeof(int*)" << "\n"
        << sizeof(long) << " sizeof(long)" << "\n"
        << sizeof(long long) << " sizeof(long long)" << "\n"
        << sizeof(void(*)()) << " sizeof(void(*)())" << "\n"
        << sizeof(int(*)(int)) << " sizeof(int(*)(int))" << std::endl;
}

ideone.com

Dostaniemy taki rezultat:

1 sizeof(char)
8 sizeof(char*)
4 sizeof(int)
8 sizeof(int*)
8 sizeof(long)
8 sizeof(long long)
8 sizeof(void(*)())
8 sizeof(int(*)(int))

Takie wyniki aplikacji skompilowanej da systemu 64-bitowego nie powinny nikogo zaskoczyć.

char zajmuje jeden bajt, ale wskaźnik na niego już 8 bajtów. Podobnie wszystkie inne wskaźniki. Nawet wskaźniki na wolne funkcje „ważą” dokładnie tyle samo – 8 bajtów.

I prawdopodobnie na tym moglibyśmy zakończyć, bo pokrywa to 99% przypadków wielkości wskaźników z jakimi kiedykolwiek będziemy pracować.

Ale pozostaje ten jeden procent.

Embedded

Świat embedded rządzi się nierzadko innymi zasadami. Podobnie jest też w tym przypadku. Tak jak pisałem wcześniej, wielkość wskaźnika zależy od tego jak szeroki (ilu bitowy) jest dostęp do pamięci. Mniejszy wskaźnik nie będzie w stanie zaadresować całej dostępnej pamięci. Większy zaś będzie marnotrawstwem – a tego nie lubimy.

Dla przykładu:

  • popularny w latach 80 układ Z80 operował na 16-bitowych wskaźnikach (w języku C)
  • oryginalny układ 8086 wykorzystywał wskaźniki 16, 20 lub 32 bitowe
  • AVRy wykorzystują 16-bitowe szyny danych dostępu do pamięci SRAM

Jak widać na zaledwie kilku powyższych przykładach widać, że rozmiar wskaźników może być różny w zależności od platformy na którą piszemy kod.

Wskaźnik na metodę

Jest jeszcze przynajmniej jeden aspekt który należy rozważyć. Wskaźniki na metody klas i struktur.

Rozważmy poniższy kod:

struct A
{
    void run() const { }
};

int main() {
    std::cout 
        << sizeof(&A::run) << " sizeof(&A::run)" << std::endl;
}

ideone.com

Jak myślicie? Jaki będzie wynik wykonania takiego kodu?
Otóż będzie to:

16 sizeof(&A::run)

Tak. 16 bajtów. Ale to nie jest jedyna odpowiedź. Ta wartość jest zależna od kompilatora i tak na przykład w Visual C++ wielkość wskaźnika na metodę klasy może wynosić 4, 8, 12 lub 16 bajtów w zależności od platformy, kompilatora i implementacji klasy.

Jak sami widzicie świat wskaźników na metody struktur czy klas to dziki świat. Wiele zależy od kompilatora, czy też samej klasy – czy dziedziczenie jest pojedyncze czy wielokrotne. A może też wirtualne? To wszystko potrafi zwiększyć rozmiar wskaźnika na metodę klasy. Jeśli chcesz dowiedzieć się więcej o wskaźnikach na metody zachęcam do przeczytania tego artykułu na codeproject.com

Podsumowanie

Nawet z pozoru proste pytanie o to, jak duże są wskaźniki w C++ może przysporzyć niejednemu z nas kłopotów, bo któż z nas przejmował się kiedyś tym, ile waży wskaźnik? Mam nadzieje, że przybliżyłem trochę bardziej naturę wskaźnika tym z Was, którzy jeszcze jej nie czuli.

Wojciech Stróżyński

Embedded Software Engineer i C++ Developer z kilkuletnim doświadczeniem. Odpowiedzialny za oprogramowanie na wielu poziomach abstrakcji – od sterowników do mikrokontrolerów po aplikacji do edycji scen 3D. W wolnych chwilach programuje, podróżuje i gra w grę.

@Wojciech Stróżyński

Angielski (4)
C++ (7)
Junior (12)
Python (3)
Regular (6)
ReverseEngineering (1)
Rozmowa (6)
Różne (5)
Senior (1)
Tips&Tricks (9)
angielski c++ junior python regular rozmowa rozmowa kwalifikacyjna