niedziela, 18 sierpnia 2013

[C/C++] operator sizeof() cz.1

Operator sizeof() - do czego służy? Na pewno większość wie, że zwraca on rozmiar danego typu w bajtach. Co ciekawe często możemy otrzymać różne wyniki. Skompilujmy poniższy kod:

#include <stdio.h>

int main(void)
{
  printf("%d\n", sizeof(char));
  printf("%d\n", sizeof(short));
  printf("%d\n", sizeof(int));
  printf("%d\n", sizeof(long));
  printf("%d\n", sizeof(long long)); // c99
  printf("%d\n", sizeof(void*));
  return 0;


U mnie wyniki są następujące: 1,2,4,8,8. Możliwe że uzyskaliście inne. Od czego zależy więc rozmiar danych? Od architektur komputerów na których program został uruchomiony, bądź systemu operacyjnego. Teoretycznie tak, jednak nie zapominajmy o tym że rozmiar danych zależy przede wszystkim od kompilatora. Przykładowo, jeśli mamy 64-bitowy procesor, możemy używać 16-bitowego kompilatora, w którym to int czy void* będzie miał rozmiar właśnie 16 bitów.

Ogólnie powinna zachodzić następująca zależność:


  • sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)

Istnieją jednak minimalne progi rozmiarowe ustalane na podstawie minimalnego zakresu:

  • sizeof(char) - minimum 1 bajt
  • sizeof(short) - minimum 2 bajty
  • sizeof(int) - minimum 2 bajty
  • sizeof(long) - miniumum 4 bajty
  • sizeof(long long) - minimum 8 bajtów (dostępne od standardu c99)

Jeśli chodzi o sizeof(void*) rozmiar zależny jest od szerokości słowa. Czyli dla kompilatora:

  • 16-bitowego - 2 bajty
  • 32-bitowego - 4 bajty
  • 64-bitowego - 8 bajtów

A jak sprawa wygląda ze strukturami? Interesująco. Zaczniemy powoli. Wszystko omawiać będę biorąc pod uwagę rozmiary danych, które występują u mnie (char: 1, short: 2, int: 4 - GCC 64bit x86).

struct A{
  char znak;
};

Tutaj sizeof(struct A) zwróci 1 - nic zaskakującego.

struct B{};

Nie wszystkie kompilatory zezwolą nam na zdefiniowanie takiej struktury (GCC pozwoli), jednak jeśli pozwolą to sizeof(struct B) zwróci 0 jeśli skompilujemy plik z rozszerzeniem *.c, natomiast gdy plik będzie miał rozszerzenie *.cpp zwróci 1.

struct C{
  char znak;    // 1 bajt
                // 1 bajt wyrównawczy - wyrównanie do 2
  short liczba; // 2 bajty
};

Wydawałoby się, że skoro sizeof(char) == 1, a sizeof(short) == 2, sizof(struct C) == 3. Sprawdźcie więc wyniki. Okazuje się, że zostaje nam zwrócony rozmiar większy niż suma rozmiarów danych użytych w strukturze. Wszystkiemu winny jest kompilator, który dodaje odpowiednią ilość bajtów w celu wyrównania danych w pamięci (data structure alignment), zapewnia to szybszy odczyt danych przez procesor. Ile zostaje dodanych bitów wyrównawczych? Dokładnie tyle by uzupełnić pamięć tak, aby wyrównać ją do wielokrotności liczby 2, w zależności od rozmiaru kolejnego pola.

Spójrzmy na to z tej perspektywy:

struct D{
  char znak;  // 1 bajt
              // 3 bajty wyrównawcze - wyrównanie do 4
  int liczba; // 4 bajty
  char znak2; // 1 bajt
              // 3 bajty wyrównawcze - wyrównanie do 4
};

Łącznie 12 bajtów, sporo jak na 2 chary i inta. Pamiętajmy także, że ostatnie pole struktury jest zawsze wyrównywane tak, aby całkowity rozmiar struktury był wielokrotnością najszerszego pola w strukturze.

struct E{
  char znak;  // 1 bajt
  char znak2; // 1 bajt
              // 2 bajty wyrównawcze - wyrównanie do 4
  int liczba; // 4 bajty
};

Tutaj mamy łącznie 8 bajtów. Przy utworzeniu 1000 obiektów uzyskalibyśmy dodatkowo kilka kB. Widać więc, że warto zwracać uwagę na kolejność zmiennych w strukturze np. przy programowaniu mikroprocesorów, gdzie pamięć jest często bardzo ograniczona. 

Brak komentarzy:

Prześlij komentarz