Rozmieszczenie zmiennych w pamięci

0

Witam.

Na wstępie pragnę zaznaczyć, że uczę się programować w C++.
Wcześniej miałem kontakt tylko z Delphi.
Temat wskaźników w Delphi potraktowałem nieco po macoszemu i szczerze mówiąc niewiele z niego zrozumiałem.
Teraz pragnę naprawić swój błąd i zrozumieć jak działa pamięć komputera.
Z pomocą pewnej książki do C++ napisałem taki programik:

#include <iostream>
#include <conio>
#pragma hdrstop

using namespace std;

int main()
{
	unsigned short Krotka = 17;
	unsigned long Dluga = 65535;
	long ZeZnakiem = -58;

	cout << "Krotka:\t\t" << Krotka;
	cout << "\nRozmiar:\t" << sizeof(Krotka);
	cout << "\nAdres:\t\t" << (long) &Krotka;

	cout << "\n\nDluga:\t\t" << Dluga;
	cout << "\nRozmiar:\t" << sizeof(Dluga);
	cout << "\nAdres:\t\t" << (long) &Dluga;

	cout << "\n\nZe znakiem:\t" << ZeZnakiem;
	cout << "\nRozmiar:\t" << sizeof(ZeZnakiem);
	cout << "\nAdres:\t\t" << (long) &ZeZnakiem;

	getch();
	return 0;
}

Po skompilowaniu otrzymałem takie coś:
160192201950392f82374b8.png

I tu pojawia się parę pytań:
*Skąd się wzięła różnica aż 6 bajtów między krótką a długą zmienną? (Dodam, że w książce różnią się tylko o 4 bajty)
*Czy zmienne są odkładane na stos od tyłu? Jeśli nie, to skąd taka kolejność adresów?
Z góry dziękuję za pomoc i pozdrawiam
J4T

1

Stos rośnie w kierunku niższych adresów pamięci i panuje tam zasada ostatni na wejściu pierwszy na wyjściu. Zatem jeśli położylibyśmy coś na stos w takiej kolejności:
push a
push b
to zmienna b będzie miała niższy adres niż a. A jak będziemy chciali zdjąc element ze stosu to zdjemie się element b ponieważ był on ostatni na wejściu. Rozmieszczenie zmiennych w pamięci jest kwestią optymalizacji kompilatora. Zatem rozmieszczenie zmiennych na dwóch różnych kompilatorach może wyglądać zupełnie inaczej.

2

Różnica zapewne wynika z paddingu - to nie jest tak że komputer sobie wrzuca wszystko w dowolnych paczkach w pamięci. Czemu? Bo procesor ma 16-32 albo 32-64 bitowe rejestry więc takie paczki potrafi odczytywać z pamięci więc nawet jak zrobisz sobie w C++ pole bitowe które będzie mało 1 bit, to ta struktura będzie nadal zajmować więcej, bo wymuszenie na procesorze wykonywania operacji na 1 bitowych kawałkach byloby strasznie nieoptymalne. W związku z tym zmienne w pamięci zwykle mają padding - wyrównanie do pewnej ilości bajtów. Czy tutaj tak jest nie wiem, ale to możliwe.

0

Jeśli dobrze rozumiem, to zmienna Długa typu unsigned long zajęła 6 bajtów, natomiast zmienna long ZeZnakiem tylko 4.
Nie znam się na architekturze procesora, ale tak na logikę to powinny mieć po tyle samo (nie ważne czy 4, czy 6, czy inne 85).

1

o_O przecież wypisujesz sizeof() więc widzisz że zmienne zajęły dokładnie tyle ile tam masz napisane. Ale inną kwestią jest jak potem to się ułoży w pamięci. Najwyraźniej pomiędzy tymi zmiennymi jest jeszcze nieużywany 2 bajtowy kawałek.

0

Porobiłem jeszcze parę testów i wynik mnie zadziwia:
1644119911503940f35e7f2.png

Wygląda na to że "nie zgadza się" tylko adres ostatniej zmiennej.
Już nie wiem co mam o tym myśleć ;/

0

Jeżeli nie programujesz sterownika ABS, albo pralki, to daj sobie z tym spokój.
Lepiej spożytkujesz czas nie myśląc o tym.

1

@Just4Trance - po prostu - to gdzie zmienne będą siedziały w pamięci to tylko i wyłączenie sprawa kompilatora. Jeśli jakaś książka twierdzi inaczej, to książka się myli.
A to że zazwyczaj będą siedziały po sobie kolejno w pamięci - szczegół implementacyjny.

2

Wyrównywanie rozmieszczenia zmiennych w pamięci przez kompilatory (ale i assemblery) ma na celu głównie optymalizację czasu dostępu.
Chodzi o to, że pamięć jest tak zbudowana, że odczytywana jest paczkami dzisiaj po 32 lub znacznie częściej 64 bity. W związku z tym adresy komórek odczytywanych w jednym cyklu odczytu i zapisu muszą być wielokrotnością 8 lub 4. Kiedyś w komputerach 8- i 16-bitowych wyrównywanie było zaledwie wielokrotnością 2, więc problemu prawie nie było. Jeżeli zmienna nie jest wyrównana, to jej odczyt na poziomie sprzętowym składa się z dwóch transferów z/do pamięci takich wyrównanych paczek, więc taki odczyt lub zapis wydaje się znacząco dłuższy. Np. dla odczytania 16 bitów rozmieszczonych na granicy dwóch takich segmentów trzeba dwa razy odczytać po 8 bajtów i z każdego z nich wziąć tylko 1 bajt. Powoduje to, że operacje na zmiennych, które nie są wyrównane są o wiele wolniejsze, więc wyrównywanie stosuje się standardowo.
Dlatego między adresami poszczególnych zmiennych mogą zdarzyć się "dziury" wyrównujące je do wielokrotności 8 lub 4.
Jest to tak istotne, że niektóre języki mają nawet słowo kluczowe, które pozwala wybrać czy dane mają być wyrównywane czy nie (np. packed). Często w róznych kompilatorach podstawowym wyborem optymalizacji jest zorientowanie na szybkość lub na oszczędność pamięci. W pierwszym wypadku wyrównywanie jest stosowane standardowo, w drugim bardzo rzadko lub wcale.
Przy programach korzystających intensywnie z pamięci i mających mało operacji z plikami czy alokacji na stercie można zauważyć sporą różnicę prędkości działania. W skrajnych wypadkach nawet do 50%. Mniej więcej o tyle ile czasu dłużej może trwać spowolnienie pracy z pamięcią.

1 użytkowników online, w tym zalogowanych: 0, gości: 1