源文件通過如下幾步生成可執行文件:html
編譯器和彙編器建立的目標文件包含:二進制代碼(指令)、源碼中的數據;連接器將多個目標文件連接成一個;裝載器吧目標文件加載到內存。ios
圖1 源文件到可執行文件的步驟程序員
經過上面的小節,咱們知道將源程序轉換爲可執行程序的步驟,典型的可執行文件分爲兩部分:小程序
源程序編譯後連接到一個以0地址爲始地址的線性或多維虛擬地址空間。並且每一個進程都擁有這樣一個空間,每一個指令和數據都在這個虛擬地址空間擁有肯定的地址,把這個地址稱爲虛擬地址(Virtual Address)。將進程中的目標代碼、數據等的虛擬地址組成的虛擬空間稱爲虛擬存儲器(Virtual Memory)。典型的虛擬存儲器中有相似的佈局:安全
以下圖所示:app
圖2 進程內存佈局less
當進程被建立時,內核爲其提供一塊物理內存,將虛擬內存映射到物理內存,這些都是由操做系統來作的。ide
討論C/C++中的內存佈局,不得不提的是數據的存儲類別!數據在內存中的位置取決於它的存儲類別。一個對象是內存的一個位置,解析這個對象依賴於兩個屬性:存儲類別、數據類型。函數
C/C++中由(auto、 extern、 register、 static)存儲類別和對象聲明的上下文決定它的存儲類別。佈局
auto和register將聲明的對象指定爲自動存儲類別。他們的做用域是局部的,諸如一個函數內,一個代碼塊{***}內等。操做了做用域,對象會被銷燬。
靜態對象能夠局部的,也能夠是全局的。靜態對象一直保持它的值,例如進入一個函數,函數中的靜態對象仍保持上次調用時的值。包含靜態對象的函數不是線程安全的、不可重入的,正是由於它具備「記憶」功能。
下面咱們分析一段代碼:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int a; 5 static int b; 6 7 void func( void ) 8 { 9 char c; 10 static int d; 11 } 12 13 int main( void ) 14 { 15 int e; 16 int *pi = ( int *) malloc ( sizeof ( int )); 17 func (); 18 func (); 19 free (pi ); 20 return (0); 21 }
程序中聲明的變量a、b、c、d、e、pi的存儲類別和生命期以下所述:
用圖表示以下:
圖3 例子的內存佈局
綜合1~4,介紹了C/C++中由源程序到可執行文件的步驟,和可執行程序的內存佈局,數據存儲類別,最後還經過一個例子來講明。
可執行程序中的變量在內存中的佈局能夠總結爲以下:
內存能夠分爲如下幾段:
前1~4引自吳秦先生的博文。
做者:吳秦
出處:http://www.cnblogs.com/skynet/
本文基於署名 2.5 中國大陸許可協議發佈,歡迎轉載,演繹或用於商業目的,可是必須保留本文的署名吳秦(包含連接).
先來看一個例子:
1 #include<iostream> 2 using namespace std; 3 4 class test 5 { 6 private: 7 char c = '1'; // 1byte 8 int i; // 4byte 9 short s = 2; // 2byte 10 }; 11 12 int main() 13 { 14 cout << sizeof(test) << endl; 15 return 0; 16 }
輸出是12.
1 #include<iostream> 2 using namespace std; 3 4 class test 5 { 6 private: 7 int i; // 4byte 8 char c = '1'; // 1byte 9 short s = 2; // 2byte 10 }; 11 12 int main() 13 { 14 cout << sizeof(test) << endl; 15 return 0; 16 }
輸出是8.
咱們能夠看到,類test和test2的成員變量徹底同樣,只是定義順序不同,卻形成了2個類佔用內存大小不同。這就是編譯器內存對齊的緣故。
一、第一個數據成員放在offset爲0的地方,之後每一個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
二、在數據成員完成各自對齊以後,類(結構或聯合)自己也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
很明顯#pragma pack(n)做爲一個預編譯指令用來設置多少個字節對齊的。值得注意的是,n的缺省數值是按照編譯器自身設置,默認爲8。其語法以下:
where:
1 | 2 | 4 | 8 | 16 Members of structures are aligned on the specified byte-alignment, or on their natural alignment boundary, whichever is less, and the specified value is pushed on the stack. nopack No packing is applied, and "nopack" is pushed onto the pack stack pop The top element on the pragma pack stack is popped. (no argument specified) Specifying #pragma pack() has the same effect as specifying #pragma pack(pop).
5.3.1 對於類test的內存空間
內存分配過程:
1)char和編譯器默認的內存缺省分割大小比較,char比較小,分配一個字節給它。
2)int和編譯器默認的內存缺省分割大小比較,int比較小,佔4字節。只能空3個字節,從新分配4個字節。
3)short和編譯器默認的內存缺省分割大小比較,short比較小,佔2個字節,分配2個字節給它。
4)對齊結束類自己也要對齊,因此最後空餘的2個字節也被test佔用。
5.3.2 對於類test2的內存空間
1)int和編譯器默認的內存缺省分割大小比較,int比較小,佔4字節。分配4個字節給int。
2)char和編譯器默認的內存缺省分割大小比較,char比較小,分配一個字節給它。
3)short和編譯器默認的內存缺省分割大小比較,short比較小,此時前面的char分配完畢還餘下3個字節,足夠short的2個字節存儲,因此short緊挨着。分配2個字節給short。
4)對齊結束類自己也要對齊,因此最後空餘的1個字節也被test佔用。
5.3.3 使用#pragma pack(n)
1 #include<iostream> 2 using namespace std; 3 4 #pragma pack(1)//設定爲1字節對齊 5 6 class test 7 { 8 private: 9 char c = '1'; //1byte 10 int i; //4byte 11 short s = 2; //2byte 12 }; 13 14 class test2 15 { 16 private: 17 int i; //4byte 18 char c = '1'; //1byte 19 short s = 2; //2byte 20 }; 21 22 int main() 23 { 24 cout << sizeof(test) << endl; 25 cout << sizeof(test2) << endl; 26 return 0; 27 }
輸出結果:
能夠看到,當咱們把編譯器的內存分割大小設置爲1後,類中全部的成員變量都緊密的連續分佈。
要嚴重參考一IBM的文章:Data alignment: Straighten up and fly right,PDF版本可從這裏下載獲得。
l 平臺緣由(移植緣由):不是全部的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。
l 性能緣由:通過內存對齊後,CPU的內存訪問速度大大提高。具體緣由稍後解釋。
圖一:
這是普通程序員心目中的內存印象,由一個個的字節組成,而CPU並非這麼看待的。
圖二:
CPU把內存當成是一塊一塊的,塊的大小能夠是2,4,8,16字節大小,所以CPU在讀取內存時是一塊一塊進行讀取的。塊大小成爲memory access granularity(粒度) 能夠把它翻譯爲「內存讀取粒度」 。
假設CPU要讀取一個int型4字節大小的數據到寄存器中,分兩種狀況討論:
1)數據從0字節開始
2)數據從1字節開始
假設內存讀取粒度爲4。
圖三:
當該數據是從0字節開始時,很CPU只需讀取內存一次便可把這4字節的數據徹底讀取到寄存器中。
當該數據是從1字節開始時,問題變的有些複雜,此時該int型數據不是位於內存讀取邊界上,這就是一類內存未對齊的數據。
圖四:
此時CPU先訪問一次內存,讀取0—3字節的數據進寄存器,並再次讀取4—5字節的數據進寄存器,接着把0字節和6,7,8字節的數據剔除,最後合併1,2,3,4字節的數據進寄存器。對一個內存未對齊的數據進行了這麼多額外的操做,大大下降了CPU性能。
這還屬於樂觀狀況了,上文提到內存對齊的做用之一爲平臺的移植緣由,由於以上操做只有有部分CPU肯幹,其餘一部分CPU遇到未對齊邊界就直接罷工了。
先看下邊一小程序:
1 #include <iostream> 2 using namespace std; 3 4 struct MyStruct 5 { 6 int a; 7 int b; 8 int c; 9 }; 10 11 int main() 12 { 13 struct MyStruct myStruct = {1, 2, 3}; 14 15 struct MyStruct *ptr = &myStruct; 16 cout << ptr->a << endl; 17 cout << ptr->b << endl; 18 cout << ptr->c << endl; 19 20 int *pstr = (int *)&myStruct; 21 cout << *pstr << endl; 22 cout << *(pstr + 1) << endl; 23 cout << *(pstr + 2) << endl; 24 25 return 0; 26 }
上邊程序中第16~18和第21~23行輸出的結果是同樣的。但若是咱們考慮到字節填充的問題時,採用pstr那種訪問方式就不大對了。因此要採用ptr那種訪問方式。
Data alignment: Straighten up and fly right
更多關於C++內存佈局請參考: