Program w C++ i jego pamięć.

Pamiętam, że w pewnym momencie nauki programowania usłyszałem magiczne pytanie- gdzie znajdują się zmiennę automatyczne. Odpowiedź była prosta. Podobnie, gdy padło pytanie dotyczące zmiennych lokowanych dynamicznie. Następnie zwykle padało pytanie o to gdzie lokowane są zmienne globalne i wtedy miałem problem.

C++ uczyłem się z książek i akademickich kursów, a i tak jedyny podział jaki miałem w głowie to stos/sterta. Pytanie o zmienne globalne momentalnie wyrywało mnie z tego kontekstu, ponieważ nie mogłem dopasować odpowiedzi do żadnego ze znanych mi obszarów. Zrobiłem wtedy nieco notatek, odnośnie tego jak wygląda pamięć w procesie (z perspektywy C++), jednak nigdy nie zebrałem tego do kupy. Jeżeli ułożenie segmentów pamięci Tobie także co jakiś czas zrywa sen z powiek, to ten artykuł powinien Ci się spodobać- spróbuję uporządkować w nim temat tak by był on zrozumiały nawet dla początkujących programistów. Jeżeli nigdy się nad tym nawet nie zastanawiałeś- to może dzięki temu tekstowi nie pozwolisz zaskoczyć się na najbliższej rozmowie kwalifikacyjnej ;) 

 Spis treści

  1. Zmienne automatyczne (stos)
  2. Zmienne alokowane dynamicznie (sterta)
  3. Zmienne globalne i statyczne (data i bss)
  4. Kod programu i biblioteki statycze (text)
  5. Obszary mapowane (biblioteki dynamiczne)
  6. Zakonczenie

 

Zacznijmy od tego, że napisaliśmy program, a następnie go uruchamiamy. Na potrzeby zrealizowania naszego programu powstaje nowy proces- to w nim wykonywane będą wszystkie nasze komendy oraz powstaną zmienne jakie stworzyliśmy w programie. Do procesu przydzielona zostaje pamięć. Początkowo wyglada to tak, że w pamięci procesu znajdują się dwa elementy- pamięć dla systemu operacyjnego, oraz dla naszego programu. Nie ma niczego innego. Pamięć przeznaczona dla systemu może mieć różną wielkość, z tego co wyczytałem to dla współczesnych systemów Windows powinno to być domyslnie 2GB oraz dla systemów Linuxowych 1GB. Dla aplikacji 32 bitowej, pamięć dla całego procesu to 4GB (więcej nie można zaadresować). Po odjęciu od 4GB  1-2 GB dla systemu, pozostaje pamięć dla naszej aplikacji. Pamięć dostępną dla aplikacji pod systemem windows możemy sprawdzić programem (VMMAP). Pamięć jaką widzimy nie jest jednak pamięcią fizyczną. Proces operuje na adresach wirtualnych, które dopiero później są tłumaczona na adresy fizyczne. Dzięki temu nam wydaje się, że pracujemy na ciągłej jednostce pamięci, a w praktyce fizyczna pamięć jest rozsiana gdzieś po RAMie (lub nawet HDD z tego co kojarzę). Zrzut pochodzi z programu w systemie Windows i tutaj też kończy się rola tego systemu w tym artykule. W dalszej częsci wpisu będziemy badać pamięć procesu na ubuntu. Zdecydowałem sie na ten krok, ponieważ informacje o procesach w tym systemie były lepiej dostępne. Być może w kolejnej części zajmiemy się dokładną analizą także na Windowsie.  Nawet jeżeli na codzień pracujesz na Widnowsie- nie przejmuj się. Tak czy inaczej warto poznać rodzaje segmentów pamięci. Strzelam w ciemno, że w systemie Windows znajdziemy ogrom podobnieństw- tam tez musi być stos, sterta etc.

2gb na start less
pamiec system
 

Omówmy zamieszczony powyżej zrzut z programu VMMAP. Jeżeli dodamy wartość z Size z wiersza Total, do wartości Size z wiersza Free to otrzymamy plus minus wspomniane wcześniej 2 giga- to pamięć dostępna w procesie dla naszej aplikacji. Jeżeli pytasz "ok, a co w przypadku apki x64?"- Free wzrośnie i to znacznie. W przypadku moich testów do wartości:  137 438 937 472K (128TB). 

Na tym etapie wiemy, że pamięć w naszym procesie dzieli się na 2 sektory- systemowy, którego nie możemy dotknąć, oraz pamięć naszej aplikacji, o której powiemy sobie nieco więcej. Poglądowo przedstawiłem to na rysunku powyżej. Niedługo będziemy rozszerzać ten rysunek o kolejne segmenty pamięci. Skoro wiemy już na czym stoimy podczas powstania naszego małego świata, czas w nim troche namieszać.

Zmienne automatyczne (stos)

Zacznijmy pisać nasz program. Na początku kilka prostych funkcji, a wraz z nimi zmienne automatyczne. Kod jest co prawda długi, ale prosty do przeanalizowania. W każdej funkcji tworzymy kilka zmiennych typu char, a następnie drukujemy ich adres: 

int main()
{
	char first_var;
	cout << "ADDRESSES IN MAIN FUNCTION:" << endl;
	cout << "address of char first_var:\t" << dec << reinterpret_cast<uintptr_t>(&amp;first_var) << endl;
	cout << endl << endl;

	memory_stack();

	getchar();
	return 0;
}



void memory_stack()
{
	char ms_a;
	char ms_b;
	char ms_c;

	cout << "ADDRESSES IN MEMORY_STACK FUNCTION:" << endl;
	cout << "address of char ms_a:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_a) << endl;
	cout << "address of char ms_b:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_b) << endl;
	cout << "address of char ms_c:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_c) << endl;
	cout << endl << endl << endl;
	func1();
	func3();

}

void func1()
{
	char f1_1;
	char f1_2;
	char f1_3;
	cout << "ADDRESSES IN FUNC1 FUNCTION:\n";
	cout << "address of char f1_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_1) << endl;
	cout << "address of char f1_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_2) << endl;
	cout << "address of char f1_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_3) << endl;
	cout << endl << endl << endl;

	func2();
}

void func2()
{
	char f2_1;
	char f2_2;
	char f2_3;
	cout << "ADDRESSES IN FUNC2 FUNCTION:\n";
	cout << "address of char f2_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_1) << endl;
	cout << "address of char f2_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_2) << endl;
	cout << "address of char f2_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_3) << endl;
	cout << endl << endl << endl;
}


void func3()
{
	char f3_1;
	char f3_2;
	char f3_3;
	cout << "ADDRESSES IN FUNC3 FUNCTION:\n";
	cout << "address of char f3_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_1) << endl;
	cout << "address of char f3_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_2) << endl;
	cout << "address of char f3_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_3) << endl;
	cout << endl << endl << endl;
	func4();
}

void func4()
{
	char f4_1;
	char f4_2;
	char f4_3;
	cout << "ADDRESSES IN FUNC4 FUNCTION:\n";
	cout << "address of char f4_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_1) << endl;
	cout << "address of char f4_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_2) << endl;
	cout << "address of char f4_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_3) << endl;
	cout << endl << endl << endl;
}

output:

ADDRESSES IN MAIN FUNCTION:
address of char first_var:      140722097202287


ADDRESSES IN MEMORY_STACK FUNCTION:
address of char ms_a:           140722097202237
address of char ms_b:           140722097202238
address of char ms_c:           140722097202239



ADDRESSES IN FUNC1 FUNCTION:
address of char f1_1:           140722097202189
address of char f1_2:           140722097202190
address of char f1_3:           140722097202191



ADDRESSES IN FUNC2 FUNCTION:
address of char f2_1:           140722097202141
address of char f2_2:           140722097202142
address of char f2_3:           140722097202143



ADDRESSES IN FUNC3 FUNCTION:
address of char f3_1:           140722097202189
address of char f3_2:           140722097202190
address of char f3_3:           140722097202191



ADDRESSES IN FUNC4 FUNCTION:
address of char f4_1:           140722097202141
address of char f4_2:           140722097202142
address of char f4_3:           140722097202143

 Jak widać w output, adresy zmiennych w func1 i func3 są takie same, podobnie w func2 i func4. Ujawnia się tutaj natura stosu. Aby zobrazować ten proces, wyobraźmy sobie, że nasza pamięć to biurko. Chcemy umieścić na biurku kilka potrzebnych nam informacji- w postaci książek (książka to nasza zmienna char). W tym celu kładziemy na biurku najpierw pierwszą książkę, potem na tej książce kolejną i tak dalej. W wyniku tego działania powstaje nam stos książek. Nie możemy wyrzucić żadnej książki ze środka stosu- bo przez to spowodujemy jego zawalenie. Możemy dodawać na jego szczyt kolejną książkę lub usuwać z jego szczytu pierwszą pozycję. Taki tryb pracy na danych nazywamy w skrócie LIFO- last in, first out. Przy pracy na buforze oznacza to, że ostatni wsadzony element, musi być pierwszym który może ten bufor opuścić.

Stos najpierw zaczynamy od zmiennych w funkcji main- są to zmenne (czy też książki ;)), które kładziemy w pamięci (na biurku) jako pierwsze- ponieważ funkcja main wywołuje się w programie zawsze na samym początku. Następnie każda kolejna wywołana funkcja rozwija stos, wkładając do niego swoje zmienne (ustawiając swoje książki). W wyniku tego działania podczas realizacji funkcji FUNC2 stos rozwija się na wysokość kilku zmiennych char (książek) . Następnie, gdy funkcja się kończy, zmienne ze stosu zostają usunięte w odwrotnej kolejności niż były tam wkładane.  Po wykonaniu func1 i func2, stos ich zmiennych zwija się (znaczy to tyle, że zmienne zostają niszczone gdy program wychodzi z określonej funkcji- skoro funkcja się skończyła, to zmienne w niej są już bezużyteczne). Następnie wywoływane są func3 i func4. Obszar pamięci, który przed chwilą został zwolniony po func1 i func2 teraz zostaje zajęty przez func3 i func4. Mówiąc obrazowo, nasz stos książek po wyjściu z func1 i func2 zmalał, ale teraz, gdy wchodzimy w func3 i func4 znowu kładziemy na nim książki a on rośnie. Jeżeli nadal masz problem z wyobrażeniem sobie działania stosu, może zamieszczona animacja okaże się pomocna. Każdy prostokąt to komórka pamięci ze zmienną. Kolor oznacza funkcję, dla której została ona zarezerwowana na stosie. Możesz kliknąć w obrazek, by go powiększyć (w kodzie zmieniłem nazwę zmiennej "a" na "first_var") .
 

 

Jeżeli widzisz, że coś tutaj nie gra to jesteś bardzo spostrzegawczy i masz rację :) Jeżeli popatrzymy na adresy zmiennych to zobaczymy, że stos rośnie w dół, bardziej jakby kopał dziurę niż rósł do góry- w przeciwieństwie do stosu książek na biurku. Aby dobrze zrozumieć te zjawisko należy dopisać do naszego obrazu pamięci nasz stos. Na samej górze, czyli na najwyższych adresach znajdują się dane systemowe. Pod nimi zaczynamy nasz stos. Skoro w wyższych adresach są danej systemowe, nasz stos musi rosnąć do niższych adresów- czyli "w dół" naszej pamięci. Teraz możemy uzupełnić nasz rysunek o nowy sektor pamięci. Pamiętajmy, że stos rozwija się i zwija podczas pracy, oraz może prosić o rozszerzenie- dlatego zaznaczyliśmy obszar w kierunku którego będzie on prosił o pamięć. 

pamiec system

 

Stos jest podstawowym miejscem w pamięci- to do niego trafiają zmienne automatyczne, argumenty z ktorymi wywołano nasz program (argv i argc), argumenty kolejnych wywoływanych funkcji, czy ich zwracana wartość. Jego konstrukcja i zasada działania są proste, dzięki czemu działa on szybko. Stos zawsze jest w centrum uwagi, dlatego teoretycznie powinien być załadowany blisko cashe naszego procesora- jeżeli nie wiesz o co chodzi to oznacza to tyle, że zmienne lokalne często mogą być szybsze niż zmienne dynamiczne. Rozmiar stosu jest przydzielany na etapie kompilacji i powinien taki pozostać do końca działania aplikacji. Co prawda możliwe jest rozszerzenie stosu podczas pracy programu, jest to jednak zależne od środowiska w ktorym pracujemy. Jeżeli spróbujemy rozszerzyć stos pomimo braku dostępnej pamięci, dostaniemy informację o jego przepełnieniu- "stack overflow".

Zmienne alokowane dynamicznie (sterta)

Przeciwieństwem stosu, który lokuje zmienne automatyczne, jest sterta, na której lokujemy nasze zmienne dynamicznie. Dynamicznie, oznacza, że w czasie kompilacji nie musimy znać rozmiaru lokowanej pamięci. To my decydujemy kiedy będziemy lokować określony obiekt, jakiej będzie on wielkości, oraz kiedy go zwolnimy. To bardzo ważne, ponieważ w czasie kompilacji nie zawsze wiemy ile pamięci będzie wymagał nasz program. Jeżeli poprosimy użytkownika o podanie nam imion wszystkich swoich dzieci to nie wiemy, czy zaalokować dane na 1 czy na 5 imion, dopóki nie uzyskamy tej informacji w trakcie trwania programu. Dodatkowo zmienne przechowywane na stercie nie są zależne od wywoływań funkcji (obiekty istnieją po opuszczeniu funkcji), a co za tym idzie sama sterta musi inaczej zarządzać swoją pamięcią niż stos. Działanie sterty można zobrazować za pomocą parkingu.

Wyobraźmy sobe parking na 50 aut. Każde miejsce mieści jeden samochód osobowy. Możemy dowolnie parkować nasze samochody dopóki mamy miejsce. Przydziałem miejsc parkingowych kieruje Pan Kierownik. Pan Kierownik to uporządkowany człowiek i wie, że najlepiej stawiać pojazdy jeden obok drugiego, by zajmowały uporządkowaną najmniejszą możliwą przestrzeń. Czasem zachodzi potrzeba, by przenieść zaparkowany pojazd gdzie indziej, dlatego Pan Kierownik zawsze zapisuje numer pod jakim parkuje kolejne pojazdy. Za każdym razem, gdy ktoś przyjeżdża autem, Pan Kierownik sprawdza, czy jest dostępne miejsce. Jeżeli jest, kierowca dostaje swój numer i w nim parkuje auto. Jeżeli na parking przyjedzie większe auto, np z przyczepką, Pan Kierownik sprawdza, czy ma dostępne dwa miejsca obok siebie- ponieważ tyle zajmuje auto z przyczepką. Jeżeli są takie miejsca, udostępnia je kierowy i zapisuje w notesiku, że na miejscu X znajduje się auto, które zajmuje 2 parkingi. Dzięki temu wie, że miejsce X oraz X+1 są zajęte. Podobnie ma się sprawa z autobusem na 8 miejsc, choć ten może być bardziej problematyczny.

Załóżmy taką sytuację: na parking wjechało dużo aut- był zapełniony w pełni, czyli 50 pojazdów, z czego następnie połowa zwolniła parking. Mamy 25 wolnych miejsc. To o wiele więcej niż 8 miec, których potrzebuje autobus. Co jednak, jeżeli do domu pojechały tylko auta z parzystymi numerami? Mamy 25 miejsc, lecz żadnego obok siebie. Mimo tylu miejsc, autobus nie może zaparkować. Nasz parking uległ fragmentacji- zajęte parkingi są bardzo rozproszone. To jeden z problemów z jakimi boryka się Pan Kierownik. Oczywiście jedno miejsce to jeden bajt pamięci, najmniejsza jednostka jaką można zaadresować w c++. Pojazdy to różne obiekty. Lokując obiekt na stercie otrzymujemy wskaźnik do niego, który stanowi nasz numer parkingu. Dzięki niemu wiemy gdzie znajduje się nasz pojazd i w każdej chwili możemy się do niego odwołać. Przy odpowiednim zarządzaniu pamięcią możemy przechowywać wiele obiektów obok siebie. Dopóki starcza nam miejsca, możemy udstępniać kolejne wskaźniki do zalokowanych danych. To ciekawe. W przeciwieństwie do stosu, na stercie nasze obiekty nie muszą mieć nazwy. Umieszczamy je w pamięci, zapamiętujemy ich adres, natomiast sama nazwa obiektu nie jest dla nas istotna. Poglądowy obraz stosu przedstawia rysunek obok. W nim żółte bajty to te zajęte przez obiekty, natomiast niebieskie to pamięć wciąż wolna. Największy obiekt jaki możemy utworzyć może mieć maksymalnie 5 bajtów, pomimo, że wolnego miejsca jest więcej. Nie naliczamy tutaj żadnego narzutu związanego z informacjami o wielkości zajętego bloku dla uproszczenia obrazu sterty.  

pamiec sterta

 

Na ten moment wystarczy wyobrażeń- przejdźmy do kodu. Sprawdźmy w praktyce gdzie lokowane są zmienne w naszym programie i jak się to ma do zmiennych lokowanych na stosie:

void memory_heap()
{
	cout << "ADDRESSES IN MEMORY_HEAP FUNCTION:\n";
	int to_create = 500;

	for (int i = 0; i < to_create; i++)
	{
		int * mem_heap = new int; // let it fly away.
		if (i % 10 == 0)
		{
			cout << "address of new char mem_heap:\t" << dec << reinterpret_cast<uintptr_t>(mem_heap) << endl;
		}
	}

	cout << endl << endl << endl;
}

 output: 

ADDRESSES IN MEMORY_HEAP FUNCTION:
address of new char mem_heap:   39608336
address of new char mem_heap:   39608656
address of new char mem_heap:   39608976
address of new char mem_heap:   39609296
address of new char mem_heap:   39609616
address of new char mem_heap:   39609936
address of new char mem_heap:   39610256
address of new char mem_heap:   39610576
address of new char mem_heap:   39610896
address of new char mem_heap:   39611216
address of new char mem_heap:   39611536
address of new char mem_heap:   39611856
address of new char mem_heap:   39612176
address of new char mem_heap:   39612496
address of new char mem_heap:   39612816
address of new char mem_heap:   39613136
address of new char mem_heap:   39613456
address of new char mem_heap:   39613776
address of new char mem_heap:   39614096
address of new char mem_heap:   39614416
address of new char mem_heap:   39614736
address of new char mem_heap:   39615056
address of new char mem_heap:   39615376
address of new char mem_heap:   39615696
address of new char mem_heap:   39616016
address of new char mem_heap:   39616336
address of new char mem_heap:   39616656
address of new char mem_heap:   39616976
address of new char mem_heap:   39617296
address of new char mem_heap:   39617616
address of new char mem_heap:   39617936
address of new char mem_heap:   39618256
address of new char mem_heap:   39618576
address of new char mem_heap:   39618896
address of new char mem_heap:   39619216
address of new char mem_heap:   39619536
address of new char mem_heap:   39619856
address of new char mem_heap:   39620176
address of new char mem_heap:   39620496
address of new char mem_heap:   39620816
address of new char mem_heap:   39621136
address of new char mem_heap:   39621456
address of new char mem_heap:   39621776
address of new char mem_heap:   39622096
address of new char mem_heap:   39622416
address of new char mem_heap:   39622736
address of new char mem_heap:   39623056
address of new char mem_heap:   39623376
address of new char mem_heap:   39623696
address of new char mem_heap:   39624016

Na potrzeby naszych małych dociekań dodałem nową funkcję: memory_heap(). W jej ciele lokuję nieco zmiennych na stercie, oraz sprawdzam co 10ty wskaźnik, by zobaczyć jaka jest tendencja (czy adresy rosną czy maleją). Jak widzimy zmienne są daleko od tych zalokowanych na stosie. Stos lokowany był w wysokich adresach i powoli schodził w dół. Natomiast dane lokowane na stercie zaczynają się nisko i pną się do góry. Możemy przypuszczać, że kiedyś się spotkają, jeżeli zalokujemy wszystkie dostępne między nimi adresy. Czy jednak na pewno tak będzie? Tego dowiemy się sprawdzając gdzie znajdują sie kolejne segmenty pamięci. Póki co dodajmy do naszego obrazu pamięci stertę. Prostokąt w środku to miejsce potencjalnego zderzenia sterty ze stosem.

Jedna ważna rzecz o stercie. Pamięć dynamiczna może być ogromna i pofragmentowana. Jeżeli w jednej części zalokujemy obiekt, a kilka funkcji dalej odwołamy się do niego, może on być daleko od pamięci podręcznej naszego procesora. Ten będzie musiał ściągnąć ten obiekt do siebie, a to może chwile potrwać. Chwila oznacza tutaj, że dane ściagane ze sterty działają nawet 100 razy wolniej niż te, które są blisko cache procka. Dlatego nie ma sensu tworzenie pojedynczych zmiennych na stercie- za to stos nadaje się do tego idealnie. Oczywiście kwestia czasu dostępu do pamięci jest bardziej złożona, postarałem się jednak naznaczyć problem, dla osób, które właśnie zastanawiały się, czy nie lepiej wszystko lokować od razu na stercie.

pamiec system

 

Zmienne globalne i statyczne (data i bss)

Dwa poprzednie tematy pojawiają się w każdej książce związanej z programowaniem. Powstaje pytanie, gdzie znajdują się zmienne globalne? Dodajmy kilka zmiennych globalnych i sprawdźmy jakie mają położenie w pamięci:

extern char  etext, edata, end;

char glob_un_1;
char glob_un_2;
char glob_un_3;
char glob_un_4;
char glob_un_5;

char glob_in_1 = 10;
char glob_in_2 = 10;
char glob_in_3 = 10;
char glob_in_4 = 10;
char glob_in_5 = 10;

char glob_un_6;
char glob_un_7;
char glob_un_8;
char glob_un_9;
char glob_un_10;

char glob_in_6 = 10;
char glob_in_7 = 10;
char glob_in_8 = 10;
char glob_in_9 = 10;
char glob_in_10 = 10;

void memory_static()
{
	cout << "ADDRESSES IN MEMORY_STATIC FUNCTION:\n";
	static char static_un_1;
	static char static_un_2;
	static char static_un_3;
	static char static_un_4;
	static char static_un_5;

	static char static_in_1 = 5;
	static char static_in_2 = 5;
	static char static_in_3 = 5;
	static char static_in_4 = 5;
	static char static_in_5 = 5;

	static char static_un_6;
	static char static_un_7;
	static char static_un_8;
	static char static_un_9;
	static char static_un_10;

	static char static_in_6 = 5;
	static char static_in_7 = 5;
	static char static_in_8 = 5;
	static char static_in_9 = 5;
	static char static_in_10 = 5;

	cout << "Global initialized:" << endl;
	cout << "address of char glob_in_1:\t" << dec << reinterpret_cast<uintptr_t>(&amp;glob_in_1) << endl;
	cout << "address of char glob_in_2:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_2) << endl;
	cout << "address of char glob_in_3:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_3) << endl;
	cout << "address of char glob_in_4:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_4) << endl;
	cout << "address of char glob_in_5:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_5) << endl;
	cout << "address of char glob_in_6:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_6) << endl;
	cout << "address of char glob_in_7:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_7) << endl;
	cout << "address of char glob_in_8:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_8) << endl;
	cout << "address of char glob_in_9:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_9) << endl;
	cout << "address of char glob_in_10:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_10) << endl;
	cout << endl;

	cout << "Static initialized:" << endl;
	cout << "address of char static_in_1:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_1) << endl;
	cout << "address of char static_in_2:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_2) << endl;
	cout << "address of char static_in_3:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_3) << endl;
	cout << "address of char static_in_4:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_4) << endl;
	cout << "address of char static_in_5:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_5) << endl;
	cout << "address of char static_in_6:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_6) << endl;
	cout << "address of char static_in_7:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_7) << endl;
	cout << "address of char static_in_8:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_8) << endl;
	cout << "address of char static_in_9:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_9) << endl;
	cout << "address of char static_in_10:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_10) << endl;
	cout << endl;

	cout << "Address of edata:\t\t" << dec << reinterpret_cast<uintptr_t>(&edata) << endl << endl;

	cout << "Global uninitialized:" << endl;
	cout << "address of char glob_un_1:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_1) << endl;
	cout << "address of char glob_un_2:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_2) << endl;
	cout << "address of char glob_un_3:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_3) << endl;
	cout << "address of char glob_un_4:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_4) << endl;
	cout << "address of char glob_un_5:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_5) << endl;
	cout << "address of char glob_un_6:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_6) << endl;
	cout << "address of char glob_un_7:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_7) << endl;
	cout << "address of char glob_un_8:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_8) << endl;
	cout << "address of char glob_un_9:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_9) << endl;
	cout << "address of char glob_un_10:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_10) << endl;
	cout << endl;

	cout << "Static uninitialized:" << endl;
	cout << "address of char static_un_1:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_1) << endl;
	cout << "address of char static_un_2:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_2) << endl;
	cout << "address of char static_un_3:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_3) << endl;
	cout << "address of char static_un_4:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_4) << endl;
	cout << "address of char static_un_5:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_5) << endl;
	cout << "address of char static_un_6:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_6) << endl;
	cout << "address of char static_un_7:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_7) << endl;
	cout << "address of char static_un_8:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_8) << endl;
	cout << "address of char static_un_9:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_9) << endl;
	cout << "address of char static_un_10:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_10) << endl;
	cout << endl;
	cout << "address of end:\t\t\t" << dec << reinterpret_cast<uintptr_t>(&::end) << endl << endl;
}

 output:

ADDRESSES IN MEMORY_STATIC FUNCTION:
Global initialized:
address of char glob_in_1:      6308000
address of char glob_in_2:      6308001
address of char glob_in_3:      6308002
address of char glob_in_4:      6308003
address of char glob_in_5:      6308004
address of char glob_in_6:      6308005
address of char glob_in_7:      6308006
address of char glob_in_8:      6308007
address of char glob_in_9:      6308008
address of char glob_in_10:     6308009

Static initialized:
address of char static_in_1:    6308010
address of char static_in_2:    6308011
address of char static_in_3:    6308012
address of char static_in_4:    6308013
address of char static_in_5:    6308014
address of char static_in_6:    6308015
address of char static_in_7:    6308016
address of char static_in_8:    6308017
address of char static_in_9:    6308018
address of char static_in_10:   6308019

Address of edata:               6308020

Global uninitialized:
address of char glob_un_1:      6308305
address of char glob_un_2:      6308306
address of char glob_un_3:      6308307
address of char glob_un_4:      6308308
address of char glob_un_5:      6308309
address of char glob_un_6:      6308310
address of char glob_un_7:      6308311
address of char glob_un_8:      6308312
address of char glob_un_9:      6308313
address of char glob_un_10:     6308314

Static uninitialized:
address of char static_un_1:    6308316
address of char static_un_2:    6308317
address of char static_un_3:    6308318
address of char static_un_4:    6308319
address of char static_un_5:    6308320
address of char static_un_6:    6308321
address of char static_un_7:    6308322
address of char static_un_8:    6308323
address of char static_un_9:    6308324
address of char static_un_10:   6308325

address of end:                 6308328

 

Podczas analizowania adresów zmiennych globalnych i statycznych widzimy pierwszą charakterystyczną rzecz. Wszystkie adresy znajdują się poniżej sterty. przypomnijmy co wiemy o naszej pamięci do tej pory: na samej górze jest stos, który zmierza w dół. Niżej jest sterta, która zmierza w górę, a jak się teraz okazuje jeszcze niżej jest nasza pamięć ze zmiennymi statycznymi i globalnymi. Jeżeli popatrzysz w kod, to zauważysz, że naumyślnie deklarowałem na przemian zmienne zainicjalizowane oraz niezainicjalizowane. Pomimo to tego, ich adresy pogrupowały się inaczej: Od najniższych do wyższych mamy kolejno najpierw zmiene zainicjalizowane, a następnie dopiero niezainicjalizowane. W środku tych grup najpierw znajdują się zmienne globalne, a następnie zmienne statyczne. Teraz już mamy pewność, że nasze zmienne globalne i statycznie nie znajdują się ani na stosie, ani na stercie. Jednocześnie wiemy, że przechowywane są w dwóch oddzielnych miejscach zależnie od tego, czy są zainicjalizowane, czy też nie. Obszar zmiennych statycznych/globalnych zainicjalizowanych nazywamy segmentem "data", natomiast zmiennych niezainicjalizowanych segmentem "bss". Zmienne w segmencie bss zawsze otrzymują automatycznie wartość 0, w przeciwieństwie do zmiennych na stosie czy stercie, które niezainicjalizowane przechowują śmieci. Dorysujmy do naszej pamięci nowe obszary. O mały włos bym zapomnił. Do sprawdzenia naszych małych eksperymentów użyłem symboli edata oraz end. Pierwszy z nich wskazuje za koniec segmentu data, natomiast drugi za koniec segmentu bss. Wartości w symbolach pokrywają się z naszymi badaniami.

pamiec system

 

Kod programu

Na naszym obrazie pamięci znajdujemy już wiele segmentów. Wiemy gdzie położone są zmienne automatyczne, statyczne, dynamiczne. W skrócie mówiąc- potrafimy powiedzieć gdzie trafi lokowana przez nas zmienna. Wciąż jednak czegoś brakuje. Zmienne zmiennymi, ale skąd nasz proces ma wiedzieć co z nimi zrobić? No własnie. Pozostaje kwestia kodu maszynowego. Aby go znaleźć dopisałem nową funkcję:  

void functions_addr()
{
	cout << endl << "Functions addresses:" << endl;
	cout << "address of getchar:\t\t" << dec << reinterpret_cast<uintptr_t>(&amp;getchar) << endl;
	cout << "address of memory_stack:\t" << dec << reinterpret_cast<uintptr_t>(&memory_stack) << endl;
	cout << "address of func1:\t\t" << dec << reinterpret_cast<uintptr_t>(&func1) << endl;
	cout << "address of func2:\t\t" << dec << reinterpret_cast<uintptr_t>(&func2) << endl;
	cout << "address of func3:\t\t" << dec << reinterpret_cast<uintptr_t>(&func3) << endl;
	cout << "address of func4:\t\t" << dec << reinterpret_cast<uintptr_t>(&func4) << endl;
	cout << "address of memory_heap:\t\t" << dec << reinterpret_cast<uintptr_t>(&memory_heap) << endl;
	cout << "address of memory_static:\t" << dec << reinterpret_cast<uintptr_t>(&memory_static) << endl;
	cout << "address of MAIN:\t\t" << dec << reinterpret_cast<uintptr_t>(&main) << endl;
	cout << "address of etext:\t\t" << dec << reinterpret_cast<uintptr_t>(&etext) << endl;
	cout << endl;
}

output:

Functions addresses:
address of getchar:            4196672
address of memory_stack:  4197164
address of func1:               4197427
address of func2:               4197672
address of func3:               4197912
address of func4:               4198157
address of memory_heap:  4198397
address of memory_static: 4198606
address of MAIN:               4197021
address of etext:               4202717

Adresy funkcji znajdują się w nowym bloku. Kod maszynowy jaki został na ich podstawie stworzony przechowywany jest w segmencie text (nazywany także segmentem kodu). W nim znajdziemy także adresy wszystkich funkcji statycznie załadowanych do naszego programu. Przykładem jest tutaj funkcja getchar, która została załączona z zewnętrznej biblioteki i dołączona do naszego programu na etapie linkowania. Co bardziej zaznajomieni z programowaniem zapytają "a co z bibliotekami dynamicznymi?". No właśnie, co z nimi? Sprawdźmy.

Biblioteki dynamiczne

Jeżeli nie chcemy, by określone funkcje zostały dołączone statycznie do naszego programu- a co za tym idzie zwiększyły jego rozmiar- możemy załaczyć określone biblioteki dynamicznie. Dzięki temu skompilowany program będzie szukał określonych funkcji dopiero w runtime (to znaczy w czasie pracy). Jeżeli kiedyś zajdzie potrzeba podmiany bibliotek na inne (np nowsze) będziemy mogli to zrobić bez potrzeby ponownej kompilacji kodu (w końcu owe funkcje nie znajdują się na stałe w naszym programie, a jedynie nasz program sięga po nie dopiero w trakcie swojej pracy). Chcesz zobaczyć jak to działa? Napisałem mała bibliotekę dynamiczną... a następnie użyłem jednej z jej funkcji- zobacz gdzie w naszej pamięci znajdzie się kod ładowany dynamicznie

// dynamic lib
#include <iostream>

extern "C" void say_hello()
{
     std::cout << "Hello! I am included from dynamic lib!" << std::endl;
}

extern "C" int add(int a, int b)
{
     return a+b;
}

output

Output from LOAD_DYNAMIC:
Hello! I am included from dynamic lib!
2+1=3
address of dynamic_say_hello:   140721104741992
address of dynamic_add:           140721104742000

Cóż za niespodzianka! Adresy znów stały się duże. Wracamy więc do okolic stosu. Jeżeli skompilujemy cały dotychczas napisany program, zobaczymy, że funkcje z bibliotek ładowanych dynamicznicznie umiejscowione są w pamięci tuż za stosem. Ten obszar służy do mapowania plików bezpośrednio do pamięci naszego programu- dzięki temu możemy szybko korzystać z plików takich, jak np wspomniana biblioteka dynamczna. O mapowaniu pamięci z pewnością wspomnimy jeszcze nie raz mówiąc o jego konkretnych zastosowaniach, jednak póki co nie warto przeciągać tematu. Najwyższa pora uzupełnić naszą mapę o nowe elementy. Na samym dole dodajemy text segment o którym mówiliśmy wcześniej, natomist między stos i stertę dodajemy segment dla bibliotek dynamicznych, oraz innych mapowanych danych.

Zakonczenie

Dodatkowo przestrzeń między niektórymi segmentami zaznaczyłem na fioletowo- to nie przypadek. Przerwy stosuje się w celu wprowadzenia pewnej losowości w położeniu danych w procesie. Nie chcemy, by ktoś z zewnątrz mógł się do nich dostać w łatwy sposób, stąd zmieniamy ich położenie przerwami o losowej wielkości.

Na ten moment to wszystkie informacje jakie uporządkowałem w sprawie pamięci w procesie. Zastanawialem się, czy nie ugryźć od razu tematu pamięci w procesorze, ale zostawię to na inny artykuł. Jeżeli macie uwagi, lub zobaczycie elementy do poprawy piszcie do mnie, z chęcią dokonam wszelkich korekt ;) 

Dla bardziej dociekliwych zostawilem na samym końcu kod całej aplikacji. Polecam pobawić się lokowaniem zmiennych na stosie obraz bibliotek dynamicznych oraz obserwować w jaki sposób dzielą się pamięcią. Mam nadzieję, że wpis się wam przyda i nieco rozjaśni położenie różnych obszarów w pamięci procesu programu c++.

proces pamiec
 

Kod programu wraz z wynikiem:

#include <iostream>
#include <iomanip>
#include <sstream>
#include <dlfcn.h>

using namespace std;

void memory_stack();
void func1();
void func2();
void func3();
void func4();
void memory_heap();
void memory_static();
void functions_addr();
void load_dynamic();


extern char  etext, edata, end;

char glob_un_1;
char glob_un_2;
char glob_un_3;
char glob_un_4;
char glob_un_5;

char glob_in_1 = 10;
char glob_in_2 = 10;
char glob_in_3 = 10;
char glob_in_4 = 10;
char glob_in_5 = 10;

char glob_un_6;
char glob_un_7;
char glob_un_8;
char glob_un_9;
char glob_un_10;

char glob_in_6 = 10;
char glob_in_7 = 10;
char glob_in_8 = 10;
char glob_in_9 = 10;
char glob_in_10 = 10;



int main()
{
	char first_var;
	cout << "ADDRESSES IN MAIN FUNCTION:" << endl;
	cout << "address of char first_var:\t" << dec << reinterpret_cast<uintptr_t>(&amp;first_var) << endl;
	cout << endl << endl;

	memory_stack();
	load_dynamic();
	memory_heap();
	memory_static();
	functions_addr();

	getchar();
	return 0;
}



void memory_stack()
{
	char ms_a;
	char ms_b;
	char ms_c;

	cout << "ADDRESSES IN MEMORY_STACK FUNCTION:" << endl;
	cout << "address of char ms_a:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_a) << endl;
	cout << "address of char ms_b:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_b) << endl;
	cout << "address of char ms_c:\t\t" << dec << reinterpret_cast<uintptr_t>(&ms_c) << endl;
	cout << endl << endl << endl;
	func1();
	func3();

}

void func1()
{
	char f1_1;
	char f1_2;
	char f1_3;
	cout << "ADDRESSES IN FUNC1 FUNCTION:\n";
	cout << "address of char f1_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_1) << endl;
	cout << "address of char f1_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_2) << endl;
	cout << "address of char f1_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f1_3) << endl;
	cout << endl << endl << endl;

	func2();
}

void func2()
{
	char f2_1;
	char f2_2;
	char f2_3;
	cout << "ADDRESSES IN FUNC2 FUNCTION:\n";
	cout << "address of char f2_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_1) << endl;
	cout << "address of char f2_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_2) << endl;
	cout << "address of char f2_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f2_3) << endl;
	cout << endl << endl << endl;
}


void func3()
{
	char f3_1;
	char f3_2;
	char f3_3;
	cout << "ADDRESSES IN FUNC3 FUNCTION:\n";
	cout << "address of char f3_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_1) << endl;
	cout << "address of char f3_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_2) << endl;
	cout << "address of char f3_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f3_3) << endl;
	cout << endl << endl << endl;
	func4();
}

void func4()
{
	char f4_1;
	char f4_2;
	char f4_3;
	cout << "ADDRESSES IN FUNC4 FUNCTION:\n";
	cout << "address of char f4_1:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_1) << endl;
	cout << "address of char f4_2:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_2) << endl;
	cout << "address of char f4_3:\t\t" << dec << reinterpret_cast<uintptr_t>(&f4_3) << endl;
	cout << endl << endl << endl;
}

void memory_heap()
{
	cout << "ADDRESSES IN MEMORY_HEAP FUNCTION:\n";
	int to_create = 500;

	for (int i = 0; i < to_create; i++)
	{
		int * mem_heap = new int; // let it fly away.
		if (i % 10 == 0)
		{
			cout << "address of new char mem_heap:\t" << dec << reinterpret_cast<uintptr_t>(mem_heap) << endl;
		}
	}

	cout << endl << endl << endl;
}

void memory_static()
{
	cout << "ADDRESSES IN MEMORY_STATIC FUNCTION:\n";
	static char static_un_1;
	static char static_un_2;
	static char static_un_3;
	static char static_un_4;
	static char static_un_5;

	static char static_in_1 = 5;
	static char static_in_2 = 5;
	static char static_in_3 = 5;
	static char static_in_4 = 5;
	static char static_in_5 = 5;

	static char static_un_6;
	static char static_un_7;
	static char static_un_8;
	static char static_un_9;
	static char static_un_10;

	static char static_in_6 = 5;
	static char static_in_7 = 5;
	static char static_in_8 = 5;
	static char static_in_9 = 5;
	static char static_in_10 = 5;

	cout << "Global initialized:" << endl;
	cout << "address of char glob_in_1:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_1) << endl;
	cout << "address of char glob_in_2:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_2) << endl;
	cout << "address of char glob_in_3:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_3) << endl;
	cout << "address of char glob_in_4:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_4) << endl;
	cout << "address of char glob_in_5:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_5) << endl;
	cout << "address of char glob_in_6:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_6) << endl;
	cout << "address of char glob_in_7:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_7) << endl;
	cout << "address of char glob_in_8:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_8) << endl;
	cout << "address of char glob_in_9:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_9) << endl;
	cout << "address of char glob_in_10:\t" << dec << reinterpret_cast<uintptr_t>(&glob_in_10) << endl;
	cout << endl;

	cout << "Static initialized:" << endl;
	cout << "address of char static_in_1:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_1) << endl;
	cout << "address of char static_in_2:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_2) << endl;
	cout << "address of char static_in_3:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_3) << endl;
	cout << "address of char static_in_4:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_4) << endl;
	cout << "address of char static_in_5:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_5) << endl;
	cout << "address of char static_in_6:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_6) << endl;
	cout << "address of char static_in_7:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_7) << endl;
	cout << "address of char static_in_8:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_8) << endl;
	cout << "address of char static_in_9:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_9) << endl;
	cout << "address of char static_in_10:\t" << dec << reinterpret_cast<uintptr_t>(&static_in_10) << endl;
	cout << endl;

	cout << "Address of edata:\t\t" << dec << reinterpret_cast<uintptr_t>(&edata) << endl << endl;

	cout << "Global uninitialized:" << endl;
	cout << "address of char glob_un_1:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_1) << endl;
	cout << "address of char glob_un_2:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_2) << endl;
	cout << "address of char glob_un_3:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_3) << endl;
	cout << "address of char glob_un_4:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_4) << endl;
	cout << "address of char glob_un_5:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_5) << endl;
	cout << "address of char glob_un_6:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_6) << endl;
	cout << "address of char glob_un_7:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_7) << endl;
	cout << "address of char glob_un_8:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_8) << endl;
	cout << "address of char glob_un_9:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_9) << endl;
	cout << "address of char glob_un_10:\t" << dec << reinterpret_cast<uintptr_t>(&glob_un_10) << endl;
	cout << endl;

	cout << "Static uninitialized:" << endl;
	cout << "address of char static_un_1:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_1) << endl;
	cout << "address of char static_un_2:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_2) << endl;
	cout << "address of char static_un_3:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_3) << endl;
	cout << "address of char static_un_4:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_4) << endl;
	cout << "address of char static_un_5:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_5) << endl;
	cout << "address of char static_un_6:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_6) << endl;
	cout << "address of char static_un_7:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_7) << endl;
	cout << "address of char static_un_8:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_8) << endl;
	cout << "address of char static_un_9:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_9) << endl;
	cout << "address of char static_un_10:\t" << dec << reinterpret_cast<uintptr_t>(&static_un_10) << endl;
	cout << endl;
	cout << "address of end:\t\t\t" << dec << reinterpret_cast<uintptr_t>(&::end) << endl << endl;
}


void functions_addr()
{
	cout << endl << "Functions addresses:" << endl;
	cout << "address of getchar:\t\t" << dec << reinterpret_cast<uintptr_t>(&getchar) << endl;
	cout << "address of memory_stack:\t" << dec << reinterpret_cast<uintptr_t>(&memory_stack) << endl;
	cout << "address of func1:\t\t" << dec << reinterpret_cast<uintptr_t>(&func1) << endl;
	cout << "address of func2:\t\t" << dec << reinterpret_cast<uintptr_t>(&func2) << endl;
	cout << "address of func3:\t\t" << dec << reinterpret_cast<uintptr_t>(&func3) << endl;
	cout << "address of func4:\t\t" << dec << reinterpret_cast<uintptr_t>(&func4) << endl;
	cout << "address of memory_heap:\t\t" << dec << reinterpret_cast<uintptr_t>(&memory_heap) << endl;
	cout << "address of memory_static:\t" << dec << reinterpret_cast<uintptr_t>(&memory_static) << endl;
	cout << "address of MAIN:\t\t" << dec << reinterpret_cast<uintptr_t>(&main) << endl;
	cout << "address of etext:\t\t" << dec << reinterpret_cast<uintptr_t>(&etext) << endl;
	cout << endl;
}

void load_dynamic()
{
	cout << "Output from LOAD_DYNAMIC:" << endl;
	void* handle = dlopen("./my_shared_lib.so", RTLD_LAZY);

	if (!handle)
	{
		cout << "handle error: " << dlerror() << endl;
		return 1;
	}

	typedef void (*fcn1_t)();
	typedef int (*fcn2_t)(int,int);

	fcn1_t dynamic_say_hello = (fcn1_t)dlsym(handle, "say_hello");
	fcn2_t dynamic_add = (fcn2_t)dlsym(handle, "add");

	if ((!dynamic_say_hello) || (!dynamic_add))
	{
	 	cout << "error: " << dlerror() << endl;
		return 2;
	}

	// uncomment to check if works fine
	// dynamic_say_hello();
	// cout << "2+1=" << dynamic_add(2, 1) << endl;

	dlclose (handle);

	cout << "address of dynamic_say_hello:\t" << dec << reinterpret_cast<uintptr_t>(&dynamic_say_hello) << endl;
	cout << "address of dynamic_add:\t\t" << dec << reinterpret_cast<uintptr_t>(&dynamic_add) << endl;
	cout << endl;
}

output:

ADDRESSES IN MAIN FUNCTION:
address of char first_var:      140726820174079


ADDRESSES IN MEMORY_STACK FUNCTION:
address of char ms_a:           140726820174029
address of char ms_b:           140726820174030
address of char ms_c:           140726820174031



ADDRESSES IN FUNC1 FUNCTION:
address of char f1_1:           140726820173981
address of char f1_2:           140726820173982
address of char f1_3:           140726820173983



ADDRESSES IN FUNC2 FUNCTION:
address of char f2_1:           140726820173933
address of char f2_2:           140726820173934
address of char f2_3:           140726820173935



ADDRESSES IN FUNC3 FUNCTION:
address of char f3_1:           140726820173981
address of char f3_2:           140726820173982
address of char f3_3:           140726820173983



ADDRESSES IN FUNC4 FUNCTION:
address of char f4_1:           140726820173933
address of char f4_2:           140726820173934
address of char f4_3:           140726820173935



Output from LOAD_DYNAMIC:
address of dynamic_say_hello:   140726820174008
address of dynamic_add:           140726820174016

ADDRESSES IN MEMORY_HEAP FUNCTION:
address of new char mem_heap:   28741648
address of new char mem_heap:   28741968
address of new char mem_heap:   28742288
address of new char mem_heap:   28742608
address of new char mem_heap:   28742928
address of new char mem_heap:   28743488
address of new char mem_heap:   28743808
address of new char mem_heap:   28744128
address of new char mem_heap:   28744448
address of new char mem_heap:   28744768
address of new char mem_heap:   28745088
address of new char mem_heap:   28745408
address of new char mem_heap:   28745728
address of new char mem_heap:   28746048
address of new char mem_heap:   28746368
address of new char mem_heap:   28746688
address of new char mem_heap:   28747008
address of new char mem_heap:   28747328
address of new char mem_heap:   28747648
address of new char mem_heap:   28747968
address of new char mem_heap:   28748288
address of new char mem_heap:   28748608
address of new char mem_heap:   28748928
address of new char mem_heap:   28749248
address of new char mem_heap:   28749568
address of new char mem_heap:   28749888
address of new char mem_heap:   28750208
address of new char mem_heap:   28750528
address of new char mem_heap:   28750848
address of new char mem_heap:   28751168
address of new char mem_heap:   28751488
address of new char mem_heap:   28751808
address of new char mem_heap:   28752128
address of new char mem_heap:   28752448
address of new char mem_heap:   28752768
address of new char mem_heap:   28753088
address of new char mem_heap:   28753408
address of new char mem_heap:   28753728
address of new char mem_heap:   28754048
address of new char mem_heap:   28754368
address of new char mem_heap:   28754688
address of new char mem_heap:   28755008
address of new char mem_heap:   28755328
address of new char mem_heap:   28755648
address of new char mem_heap:   28755968
address of new char mem_heap:   28756288
address of new char mem_heap:   28756608
address of new char mem_heap:   28756928
address of new char mem_heap:   28757248
address of new char mem_heap:   28757568



ADDRESSES IN MEMORY_STATIC FUNCTION:
Global initialized:
address of char glob_in_1:      6308008
address of char glob_in_2:      6308009
address of char glob_in_3:      6308010
address of char glob_in_4:      6308011
address of char glob_in_5:      6308012
address of char glob_in_6:      6308013
address of char glob_in_7:      6308014
address of char glob_in_8:      6308015
address of char glob_in_9:      6308016
address of char glob_in_10:     6308017

Static initialized:
address of char static_in_1:    6308018
address of char static_in_2:    6308019
address of char static_in_3:    6308020
address of char static_in_4:    6308021
address of char static_in_5:    6308022
address of char static_in_6:    6308023
address of char static_in_7:    6308024
address of char static_in_8:    6308025
address of char static_in_9:    6308026
address of char static_in_10:   6308027

Address of edata:                    6308028

Global uninitialized:
address of char glob_un_1:      6308305
address of char glob_un_2:      6308306
address of char glob_un_3:      6308307
address of char glob_un_4:      6308308
address of char glob_un_5:      6308309
address of char glob_un_6:      6308310
address of char glob_un_7:      6308311
address of char glob_un_8:      6308312
address of char glob_un_9:      6308313
address of char glob_un_10:     6308314

Static uninitialized:
address of char static_un_1:    6308316
address of char static_un_2:    6308317
address of char static_un_3:    6308318
address of char static_un_4:    6308319
address of char static_un_5:    6308320
address of char static_un_6:    6308321
address of char static_un_7:    6308322
address of char static_un_8:    6308323
address of char static_un_9:    6308324
address of char static_un_10:  6308325

address of end:                       6308328


Functions addresses:
address of getchar:                  4196752
address of memory_stack:        4197264
address of func1:                     4197527
address of func2:                     4197772
address of func3:                     4198012
address of func4:                     4198257
address of memory_heap:        4198497
address of memory_static:       4198706
address of MAIN:                     4197101
address of etext:                     4202829

 


 

źródła:
https://pl.wikipedia.org/wiki/Przepe%C5%82nienie_stosu
http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/
http://www.geeksforgeeks.org/memory-layout-of-c-program/
http://stackoverflow.com/questions/4308996/finding-the-address-range-of-the-data-segment
http://programowanie.opole.pl/archives/1263#
http://stackoverflow.com/questions/2134411/any-tools-for-knowing-the-layout-segments-of-running-process-in-windows
http://stackoverflow.com/questions/1966920/more-info-on-memory-layout-of-an-executable-program-process
http://4programmers.net/C/Artyku%C5%82y/Linux_-_Biblioteki_%C5%82adowane_dynamicznie
https://pl.wikipedia.org/wiki/Segment_kodu