<h4><strong>1.變量的存儲類別</strong></h4>
從變量值存在的角度來分,能夠分爲靜態存儲方式和動態存儲方式。所謂靜態存儲方式指在程序運行期間由系統分配固定的存儲空間的方式(<strong>程序開始執行時分配,在程序完畢時釋放,在程序過程當中它們佔據國定的存儲單元,而不是動態分配和釋放</strong>)。而動態存儲方式在運行期間根據須要進行動態存儲方式(<strong>在程序過程當中申請和釋放的一些空間</strong>)。內存中供用戶使用的存儲空間分爲:程序區、靜態存儲區和動態存儲區。程序放在程序區,數據放在靜態存儲區和動態存儲區。在動態存儲區存放函數形式參數、自動變量(未加static聲明的局部變量)和函數調用時的現場保護和返回地址。
在C語言中每個變量和函數兩個屬性:數據類型和數據的存儲類別。存儲方式分爲兩大類:靜態存儲和動態存儲。具體保護:自動的(auto)、靜態的(static)、寄存器的(register)、外部的(extern)。下面分別對上面關鍵字進行說明。
(1)auto:這個關鍵字是默認的,這個關鍵字屬於動態存儲區,聲明時不進行默認初始化。
(2)static:若是但願局部變量調用結束後不消失保留此值(即加上改關鍵字在靜態區分配空間存儲),有默認初值。靜態局部變量在編譯時賦初值(即賦初值一次),之後每次運行保留上次的結果。若是是全局變量加上static時,就表示該變量的做用域只限於本模塊。(全局變量默認存儲在靜態存儲區)。一樣,在多文件中爲了防止名字衝突也能夠將函數聲明爲static,這樣只在文件內部引用
(3)register:通常狀況下變量時存在內存中,程序用到變量時發出指令將內存中的該變量送到運算器中,通過運算,再存儲到內種。這樣若是頻繁的使用某變量就將其定義爲register類型(自由局部自動變量和函數形式參數),直接放在CPU中,提升效率。(通常的編譯器爲用戶考慮這個問題,通常不須要考慮)
(4)extern:用來聲明外部變量和外部函數(和static一種用法相對),extern只是聲明而不是定義。兩種狀況:同一文件中聲明extern(表示變量定義在當前引用的後面);多文件中聲明外部變量(表示該變量定義在文件外部)。函數聲明也是如此(通常狀況函數聲明都沒有extern,省略的)。ios
<strong>注:類具備封裝性,因此類中的成員均不能使用關鍵字extern、auto或register限定其存儲類型。</strong>程序員
<h4><strong>2.C語言內存分配機制</strong></h4>
(1)<strong>棧(Stack)</strong>:位於函數內的局部變量(包括函數實參),由編譯器負責分配釋放,函數結束,棧變量失效。算法
(2)<strong>堆(Heap)</strong>:由程序員用malloc/calloc/realloc分配,free釋放。若是程序員忘記free了,則會形成內存泄露,程序結束時該片內存會由OS回收,但程序只要不結束,就有可能形成內存泄露。(程序員負責分配和釋放)數組
(3)<strong>全局區/靜態區(Global Static Area)</strong>: 全局變量和靜態變量存放區,程序一經編譯好,該區域便存在。而且在C語言中初始化的全局變量和靜態變量和未初始化的放在相鄰的兩個區域(在C++中,因爲全局變量和靜態變量編譯器會給這些變量自動初始化賦值,因此沒有區分了)。因爲全局變量一直佔據內存空間且不易維護,推薦少用。程序結束時釋放。數據結構
(4)<strong>C風格字符串常量存儲區</strong>: 專門存放字符串常量的地方,程序結束時釋放。函數
(5)<strong>程序代碼區</strong>:存放程序二進制代碼的區域。
<pre lang="c" line="1" escaped="true">實例1:
int a = 0; //全局初始化區
char *p1; //全局未初始化區(C++中則初始化爲NULL)
int main()
{
int b; //b分配在棧上,整型
char s[] = "abc"; //s分配在棧上,char *類型;"abc\0"分配在棧上,運行時賦值,函數結束銷燬
char *p2; //p2分配在棧上,未初始化
char *p3 = "123456"; //p3指向"123456"分配在字符串常量存儲區的地址,編譯時肯定 p3存儲在棧上
static int c = 0; //c在全局(靜態)初始化區,能夠屢次跨函數調用而保持原值
p1 = (char *)malloc(10); //p1在全局未初始化區,指向分配得來得10字節的堆區地址
p2 = (char *)malloc(20); //p2指向分配得來得20字節的堆區地址
strcpy(p1, "123456"); //"123456"放在字符串常量存儲區,編譯器可能會將它與p3所指向的"123456"優化成一塊
return 0;
}</pre>
<h4>3.C++語言內存分配機制</h4>
在C++語言中,與C相似,不過也有所不一樣,內存主要分爲以下5個存儲區:
(1)棧(Stack):位於函數內的局部變量(包括函數實參),由編譯器負責分配釋放,函數結束,棧變量失效。
(2)堆(Heap):這裏與C不一樣的是,該堆是由new申請的內存,由delete或delete[]負責釋放。
(3)自由存儲區(Free Storage):由程序員用malloc/calloc/realloc分配,free釋放。若是程序員忘記free了,則會形成內存泄露,程序結束時該片內存會由OS回收。
(4)全局區/靜態區(Global Static Area): 全局變量和靜態變量存放區,程序一經編譯好,該區域便存在。在C++中,因爲全局變量和靜態變量編譯器會給這些變量自動初始化賦值,因此沒有區分了初始化變量和未初始化變量了。須要說明一點,全局靜態變量和局部靜態變量都是存儲在同一個靜態區(全局區),只是做用域不一樣。
(5)常量存儲區: 這是一塊比較特殊的存儲區,專門存儲不能修改的常量(通常是const修飾的變量,或是一些常量字符串)。
優化
<h4>4.堆與棧的區別</h4>
(1)棧
具體的講,現代計算機(馮諾依曼串行執行機制),都直接在代碼低層支持棧的數據結構。這體如今,有專門的寄存器指向棧所在的地址(SS,堆棧段寄存器,存放堆棧段地址);有專門的機器指令完成數據入棧出棧的操做(彙編中有PUSH和POP指令)。
這種機制的特色是效率高,但支持數據的數據有限,通常是整數、指針、浮點數等系統直接支持的數據類型,並不直接支持其餘的數據結構(能夠自定義棧結構支持多種數據類型)。由於棧的這種特色,對棧的使用在程序中很是頻繁的 。對子程序的調用就是直接利用棧完成的。機器的call指令裏隱含了把返回地址入棧,而後跳轉至子程序地址的操做,而子程序的ret指令則隱含從堆棧中彈出返回地址並跳轉之的操做。
C/C++中的函數自動變量就是直接使用棧的例子,這也就是爲何當函數返回時,該函數的自動變量自動失效的緣由,於是要避免返回棧內存和棧引用,以避免內存泄露。
(2)堆
和棧不一樣的是,堆得數據結構並非由系統(不管是機器硬件系統仍是操做系統)支持的,而是由函數庫提供的。基本malloc/calloc/realloc/free函數維護了一套內部的堆數據結構(在C++中則增長了new/delete維護)。
當程序用這些函數去得到新的內存空間時,這套函數首先試圖從內部堆中尋找可用的內存空間(常見內存分配算法有:首次適應算法、循環首次適應算法、最佳適應算法和最差適應算法等)。若是沒有可用的內存空間,則試圖利用系統調用來動態增長程序數據段的內存大小,新分配獲得的空間首先被組織進內部堆中去,而後再以適當的形式返回給調用者。當程序釋放分配的內存空間時,這片內存空間被返回到內部堆結構中,可能會被適當的處理(好比空閒空間合併成更大的空閒空間),以更適合下一次內存分配申請。
這套複雜的分配機制實際上至關於一個內存分配的緩衝池(Cache),使用這套機制有以下緣由:系統調用可能不支持任意大小的內存分配。有些系統的系統調用只支持固定大小及其倍數的內存請求(按頁分配),這樣的話對於大量的小內存分配來講會形成浪費;系統調用申請內存多是代價昂貴的。 系統調用可能涉及到用戶態和核心態的轉換;沒有管理的內存分配在大量複雜內存的分配釋放操做下很容易形成內存碎片。
(3)棧和堆的對別
從以上介紹中,它們有以下區別:
a、棧是系統提供的功能,特色是快速高效,缺點是由限制,數據不靈活;而堆是函數庫提供的功能,特色是靈活方便,數據適應面廣,可是效率有必定下降。
b、棧是系統數據結構,對於進程/線程是惟一的;堆是函數庫內部數據結構,不必定惟一。不一樣堆分配的內存沒法互相操做。
c、棧空間分靜態分配和動態分配,通常由編譯器完成靜態分配,自動釋放,棧的動態分配是不被鼓勵的;堆得分配老是動態的,雖然程序結束時全部的數據空間都會被釋放回系統,可是精確的申請內存/釋放內存匹配是良好程序的基本要素。
d、碎片問題:對於堆來說,頻繁的new/delete等操做勢必會形成內存空間的不連續,從而形成大量的碎片,使程序的效率下降;對於棧來說,則不會存在這個問題,由於棧是後進先出(LIFO)的隊列。
e、生長方向:堆的生長方向是向上的,也就是向這內存地址增長的方向;對於棧來說,生長方向倒是向下的,是向着內存地址減小的方向增加。
f、分配方式:堆都是動態分配的,沒有靜態分配的堆;棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配則由alloca函數進行分配,可是棧的動態分配和堆不一樣,它的動態分配是由編譯器進行釋放,無需咱們手工實現。
g、分配效率:棧是機器系統提供的數據結構,計算機在底層提供支持,分配有專門的堆棧段寄存器,入棧出棧有專門的機器指令,這些都決定了棧的高效率執行。而堆是由C/C++函數庫提供的,機制比較複雜,有不一樣的分配算法,易產生內存碎片,須要對內存進行各類管理,效率比棧要低不少。spa
操作系統
實例2:線程
看下面的一小段代碼,體會堆與棧的區別:
<pre lang="c" line="1" escaped="true">
int foo()
{
//其他代碼
int *p = new int[5];
//其他代碼
return 0;
}
</pre>
其中的語句int *p = new int[5];就包含了堆與棧。其中new關鍵字分配了一塊堆內存,而指針p自己所佔得內存爲棧內存(通常4個字節表示地址)。這句話的意思是在棧內存中存放了一個指向一塊堆內存的指針p。在程序中先肯定在堆中分配內存的大小,而後調用new關鍵字分配內存,最後返回這塊內存首址,放入棧中。這段代碼在VC6下的彙編代碼爲:
<blockquote>00401028 push 14h
0040102A call operator new(00401060)
0040102F add esp,4
00401032 mov dword ptr [ebp-8],eax
00401035 mov eax,dword ptr [ebp-8]
00401038 mov dword ptr [ebp-4],eax</blockquote>
若是須要釋放內存,這裏咱們須要使用delete[] p,告訴編譯器,我要刪除的是一個數組。
<h4>5.一個很是經典的例子</h4>
實例3:
看下面的一小段代碼,試着找出其中的錯誤:
<pre lang="c" line="1" escaped="true">
#include<iostream>
using namespace std;
int main(){chara[] ="hello";a[0]= 'X';cout<< a<< endl;char*p ="world";p[0]= 'X';cout<< p<< endl;return0;}</pre>發現問題了嗎?是的,字符數組a的容量是6個字符,其內容爲"hello\0"。a的內容時能夠改變的,好比a[0]='X',由於其是在棧上分配的,也就是在運行時肯定的內容。可是指針p指向的字符串"world"分配在字符串常量存儲區,內容爲"world\0",常量字符串的內容時不能夠修改的。從語法上來講,編譯器並不以爲語句p[0]='X'有什麼問題,可是在運行時則會出現"accessviolation"非法內存訪問的問題。