一攒菠、內(nèi)存分區(qū):數(shù)據(jù)區(qū)+代碼區(qū)+堆區(qū)+棧區(qū)
1、數(shù)據(jù)區(qū):分為靜態(tài)數(shù)據(jù)區(qū)歉闰,全局變量區(qū)的存儲是放在一塊的辖众。
即static,const修飾的變量和敬、常量凹炸、全局變量都定義在此區(qū),此區(qū)定義的變量未初始化昼弟,系統(tǒng)則會自動初始化為0
初始化的全局變量和靜態(tài)變量在一塊區(qū)域啤它, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域,程序結束后由系統(tǒng)釋放空間
const:修飾的變量只能讀不能修改他的值舱痘,表現(xiàn)的為一個常量的特性变骡。他只能在初始化的時候給他賦值,其他任何時候都不能給他賦值芭逝。
2塌碌、代碼區(qū):存放程序中的普通代碼(存放函數(shù)體的二進制代碼)。
3铝耻、堆區(qū)(heap):一般由程序員手動申請以及釋放, 若程序員不釋放蹬刷,程序結束時可能由OS回收 注意它與數(shù)據(jù)結構中的堆是兩回事瓢捉,分配方式類似于鏈表。
有malloc()/calloc()/recalloc()/new()來分配內(nèi)存办成,
生命周期有free()/delete()決定何時釋放分配的內(nèi)存泡态,此區(qū)使用靈活,空間相對較大迂卢。
4某弦、棧區(qū)(stack): 該區(qū)是有編譯器自動分配的一塊內(nèi)存區(qū)域桐汤,存放函數(shù)的參數(shù)值、局部變量的值等靶壮,甚至函數(shù)的調(diào)用過程都是用棧來完成怔毛,其操作方式類似于數(shù)據(jù)結構中的棧
效率高,有編譯器自動分配和釋放(函數(shù)開始的時候分配腾降,結束的時候釋放)
5拣度、文字常量區(qū):常量字符串就是放在這里 程序結束后由系統(tǒng)釋放空間
下面的例子可以完全展示不同的變量所占的內(nèi)存區(qū)域:
//main.cpp
int a = 0; 全局初始化區(qū)
char *p1; 全局未初始化區(qū)
void main()
{
int b; //棧中
char s[] = "abc"; //棧中
char *p2; //棧中
char *p3 = "123456"; //123456\0在文字常量區(qū),p3在棧上
static int c =0螃壤; //全局(靜態(tài))初始化區(qū)
//以下分配得到的10和20字節(jié)的區(qū)域就在堆區(qū)
p1 = (char *)malloc(10);
p2 = new char[20];//(char *)malloc(20);
strcpy(p1, "123456"); //123456\0放在常量區(qū)抗果,編譯器可能會將它與p3所指向的"123456"優(yōu)化成一個地方
}
注意:strcpy()函數(shù)用法:定義一個字符串char a[20],和一個字符串c[]="i am a teacher!";把c復制到a中就可以這樣用:strcpy(a,c);
char *p="zxcvbnm";
(p+1)='d';//修改第二個字符 因為p和“zxcvbnm”沒有保存在同一個區(qū),不能修改奸晴。斷錯誤
char *q="zxcvbnm"; //兩哥指針的地址相同
指針p 和指針 q都保存在棧區(qū)冤馏,但字符串zxcvbnm保存在文字常量區(qū),因為沒有保存在一個區(qū)寄啼,通過指針獲取某個字符是不對的
并且因為在文字常量區(qū)已經(jīng)開辟存放zxcvbnm的空間逮光,在賦值zxcvbnm的時候不會再開辟空間,會把第一次開辟的地址賦予他們辕录。
****手動在內(nèi)存上分配一塊空間:在堆上用malloc()函數(shù)分配一塊連續(xù)空間
函數(shù)原型:(void *)malloc(int size)
頭文件:malloc.h/stdlib.h
解析:內(nèi)存分配好了之后睦霎,malloc函數(shù)返回這塊內(nèi)存的首地址,默認為void *型走诞。故需要強制轉(zhuǎn)換成你需要的類型副女,括號里是您需要分配的內(nèi)存的字節(jié)數(shù)
例:char *p=(char *)malloc(100)
注:所申請的內(nèi)存必須小于堆上面某一整塊內(nèi)存,才能成功蚣旱,故malloc函數(shù)請一塊內(nèi)存有可能是不成功的碑幅,所以我們需要判斷一下他是否成功,
用if(NULL!=p)來驗證是否分配成功, 通常是成功的
****手動釋放動態(tài)內(nèi)存:
free(p)塞绿,同時沟涨,p=NULL;
釋放p所指向的那塊內(nèi)存异吻,這里雖然把p指向的那塊內(nèi)存給釋放掉了裹赴,但p的值仍沒有變,這個地址還是存在的诀浪,只是不能再訪問這個內(nèi)存棋返,所以再次使用它之前先要置NULL。
二雷猪、棧(stack)和堆(heap)具體的區(qū)別
1睛竣、在申請方式上
棧(stack): 現(xiàn)在很多人都稱之為堆棧,這個時候?qū)嶋H上還是指的棧它由編譯器自動管理求摇,無需我們手工控制
例如射沟,聲明函數(shù)中的一個局部變量 int b 系統(tǒng)自動在棧中為b開辟空間殊者;在調(diào)用一個函數(shù)時,系統(tǒng)自動的給函數(shù)的形參變量在棧中開辟空間
堆(heap): 申請和釋放由程序員控制验夯,并指明大小容易產(chǎn)生memory leak
在C中使用malloc函數(shù)
如:p1 = (char *)malloc(10);
在C++中用new運算符
如:p2 = new char[20];//(char *)malloc(10);
但是注意p1本身在全局區(qū)猖吴,而p2本身是在棧中的,只是它們指向的空間是在堆中
2簿姨、申請后系統(tǒng)的響應上
棧(stack):只要棧的剩余空間大于所申請空間距误,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出
堆(heap): 首先應該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表扁位,當系統(tǒng)收到程序的申請時准潭, 會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點域仇,
然后將該結點從空閑結點鏈表中刪除刑然,并將該結點的空間分配給程序。另外暇务,對于大多數(shù)系統(tǒng)泼掠,會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,
這樣垦细,代碼中的delete或free語句才能正確的釋放本內(nèi)存空間择镇。另外,由于找到的堆結點的大小不一定正好等于申請的大小括改,
系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中
3腻豌、申請大小的限制
棧(stack):在Windows下,棧是向低地址擴展的數(shù)據(jù)結構,是一塊連續(xù)的內(nèi)存的區(qū)域嘱能。
這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預先規(guī)定好的吝梅,
在WINDOWS下,棧的大小是2M(也有的說是1M惹骂,總之是一個編譯時就確定的常數(shù))苏携,如果申請的空間超過棧的剩余空間時,將提示overflow
因此对粪,能從棧獲得的空間較小 例如右冻,在VC6下面,默認的椫茫空間大小是1M(好像是纱扭,記不清楚了)當然,我們可以修改:打開工程茫死,依次操作菜單如下:
Project->Setting->Link跪但,在Category 中選中Output履羞,然后在Reserve中設定堆棧的最大值和commit大的值峦萎,可能增加內(nèi)存的開銷和啟動時間屡久。
堆(heap): 堆是向高地址擴展的數(shù)據(jù)結構,是不連續(xù)的內(nèi)存區(qū)域(空閑部分用鏈表串聯(lián)起來)爱榔。正是由于系統(tǒng)是用鏈表來存儲空閑內(nèi)存被环,自然是不連續(xù)的,
而鏈表的遍歷方向是由低地址向高地址详幽,一般來講在32位系統(tǒng)下筛欢,堆內(nèi)存可以達到4G的空間,從這個角度來看堆內(nèi)存幾乎是沒有什么限制的由此可見唇聘,
堆獲得的空間比較靈活版姑,也比較大。
4迟郎、分配空間的效率上
棧(stack):棧是機器系統(tǒng)提供的數(shù)據(jù)結構剥险,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行宪肖,這就決定了棧的效率比較高
但程序員無法對其進行控制
堆(heap):是C/C++函數(shù)庫提供的表制,由new或malloc分配的內(nèi)存,一般速度比較慢控乾,而且容易產(chǎn)生內(nèi)存碎片顯然么介,堆的效率比棧要低得多
5、堆和棧中的存儲內(nèi)容
棧(stack):在函數(shù)調(diào)用時蜕衡,第一個進棧的是主函數(shù)中子函數(shù)調(diào)用后的下一條指令(子函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址壤短,然后是子函數(shù)的各個形參在大多數(shù)的C編譯器中,
參數(shù)是由右往左入棧的衷咽,然后是子函數(shù)中的局部變量鸽扁。
注意:靜態(tài)變量是不入棧的。 當本次函數(shù)調(diào)用結束后镶骗,局部變量先出棧桶现,然后是參數(shù),最后棧頂指針指向最開始存的地址鼎姊,
也就是主函數(shù)中子函數(shù)調(diào)用完成的下一條指令骡和,程序由該點繼續(xù)運行
堆(heap):一般是在堆的頭部用一個字節(jié)存放堆的大小,堆中的具體內(nèi)容有程序員安排
6相寇、存取效率的比較
這個應該是顯而易見的拿棧上的數(shù)組和堆上的數(shù)組來說:
void main()
{
int arr[5]={1,2,3,4,5};
int *arr1;
arr1=new int[5];
for (int j=0;j<=4;j++)
{
arr1[j]=j+6;
}
int a=arr[1];
int b=arr1[1];
}
上面代碼中慰于,arr1(局部變量)是在棧中,但是指向的空間確在堆上唤衫,兩者的存取效率婆赠,當然是arr高,因為arr[1]可以直接訪問佳励,
但是訪問arr1[1]休里,首先要訪問數(shù)組的起始地址arr1蛆挫,然后才能訪問到arr1[1]
三、內(nèi)存的釋放:
學過C語言的都知道妙黍,內(nèi)存分配了用完之后是要釋放的悴侵,都是到malloc和calloc函數(shù)以及free函數(shù)。那么分配了內(nèi)存之后是不是真就free(pointer)這么簡單呢拭嫁?
這里提及要注意的地方:參數(shù)pointer必須是調(diào)用malloc或calloc函數(shù)后返回的指針可免,而給free函數(shù)傳遞其它的值可能會造成死機或者結果是災難性的。
重點是指針的值做粤,而不是用來申請動態(tài)內(nèi)存的指針本身浇借。
可以看下代碼,
假如先前有void * p =malloc(sizeof(double)*6);
也有double * dp=(double )malloc(sizeof(double)6));
那么此刻如果free(dp)就會出現(xiàn)不可預知的錯誤怕品,free(p)是正確的逮刨,
若又p=dp,(或者p=(void *)dp),然后free(p)也是正確的
所謂災難性:無非就是釋放內(nèi)存中出現(xiàn)把不該釋放的東西給釋放了,然后引起了一些問題堵泽。
那么修己,怎么來驗證free(dp)就是錯誤的呢?這也許是個內(nèi)存泄露的問題迎罗,呵呵睬愤。
可以試下這樣一段代碼:
for(;;)
{
double * p=malloc(sizeof(double)*6);
free(p);
}
然后,看看你的內(nèi)存是否超支(不夠)了纹安?
正確的說法:
假如先前有double * p =malloc(sizeof(double)*6);
那么此刻如果free(p+1)就會出現(xiàn)不可預知的錯誤尤辱,free(p)是正確的,
原因是:分配內(nèi)存函數(shù)對該地址值及對應的內(nèi)存塊有記憶,而p+1不在記憶列表里厢岂,所以free(p+1)會引發(fā)災難性錯誤
可以試下這樣一段代碼:
for(;;)
{
double * p=malloc(sizeof(double)*6);
free(p+1);
}
再看看realloc函數(shù)光督,它可以用來重新分配經(jīng)m,c,r三者分配的內(nèi)存。那么重新分配真的是給一塊新的地址嘛塔粒?
事實上并不是這樣的结借,r有兩個參數(shù),一個是指針卒茬,引用之前分配的內(nèi)存船老,重新分配的內(nèi)存是在原來基礎之上,大小則由第二個參數(shù)決定圃酵。
也就是說柳畔,如果你家庭總收入6000元,總管(通常是母的)給兒子分配了1000元的零花錢郭赐,現(xiàn)在由于一些"不可抗力"因素薪韩,
要重新分配money,那么,傳遞參數(shù)realloc(1000元的地址,newsize),newsize<=1000U。而本質(zhì)上是將兒子手中的money根據(jù)newsize抽走一部分俘陷,然后剩下的會做一些處理张惹。
動態(tài)內(nèi)存分配的一些原則:
1、需要時分配岭洲,用完就釋放,特別是堆上的(資源很有限)坎匿。
2盾剩、避免分配大量小塊內(nèi)存,因為堆上內(nèi)存的分配由于有系統(tǒng)開銷替蔬,所以分配許多的小內(nèi)存比分配幾塊大內(nèi)存開銷要大告私,而已不便于釋放和管理。
3承桥、編程的時候始終把用戶有限的內(nèi)存放在心上驻粟,分配了就要考慮在哪里釋放。
4凶异、循環(huán)中分配內(nèi)存一定要小心翼翼
5蜀撑、釋放內(nèi)存之前,確保不會無意中覆蓋堆上分配的內(nèi)存地址剩彬,否則會出現(xiàn)內(nèi)存泄露
//手動分配一塊內(nèi)存給學生酷麦,輸入學生的個人基本信息,并打印出來喉恋。
include <stdio.h>
include <malloc.h>
struct student
{
int num;
char *name;
char sex;
float score;
};
void main()
{
struct student *p=(struct student *)malloc(sizeof(struct student));
if(NULL==p)
{
printf("is wrong");
return ;
}
p->num=1;
p->name="xxx";
p->sex='m';
p->score=90;
printf("%d %s %c %.1f",p->num,p->name,p->sex,p->score);
}
//手動分配的內(nèi)存只能通過指向他的指針來訪問他沃饶,所以這個指針的值不要搞丟了
struct node
{
int data;
struct node *next;
};
struct node *head =(struct noode *)malloc(sizeof(struct node));
struct node *p =(struct noode *)malloc(sizeof(struct node));
head->next=p;
p->next=q;
q->next=NULL;
創(chuàng)建一個鏈表鏈接
include <stdio.h>
include <malloc.h>
struct node
{
int data;//數(shù)據(jù)域
struct node *next;//指針域
};
struct node * create()
{
int n,i;
printf("請輸入要創(chuàng)建鏈表的節(jié)點數(shù)");
scanf("%d",&n);
getchar();
//創(chuàng)建頭節(jié)點
struct node *head=(struct node *)malloc(sizeof(struct node));
//新建p節(jié)點,首節(jié)點
struct node *p =(struct node *)malloc(sizeof(struct node));
printf("請輸入數(shù)據(jù):");
scanf("%d",&p->data);
getchar();
//鏈接頭節(jié)點和首節(jié)點
head->next=p;
for(i=1;i<n;i++)
{
//新建q節(jié)點
struct node *q =(struct node *)malloc(sizeof(struct node));
printf("請輸入數(shù)據(jù):");
scanf("%d",&q->data);
getchar();
p->next=q;
p=q;
}
p->next=NULL;
return head;
}
void print(struct node *head)
{
struct node *p=head->next;
while(p)
{
printf("%d\n",p->data);
p=p->next;
}
}
//頭插
struct node * T_insert(struct node *head)
{
struct node *q =(struct node *)malloc(sizeof(struct node));
printf("input data:");
scanf("%d",&q->data);
getchar();
q->next=head->next;//先連接后面的節(jié)點轻黑,再連接前面的節(jié)點
head->next=q;
return head;
}
void main()
{
struct node *head;
head=create();
print(head);
head=T_insert(head);
print(head);
}