動(dòng)態(tài)內(nèi)存分配(動(dòng)態(tài)存儲(chǔ)期)
在程序執(zhí)行并使用該變量的時(shí)候分配內(nèi)存空間,使用完畢立即釋放.
動(dòng)態(tài)內(nèi)存分配就 是指在程序執(zhí)行的過(guò)程中動(dòng)態(tài)地分配或者回收存儲(chǔ)空間的分配內(nèi)存的方法。動(dòng)態(tài)內(nèi)存分配不像數(shù)組等靜態(tài)內(nèi)存分配方法那樣需要預(yù)先分配存儲(chǔ)空間,而是由系統(tǒng)根據(jù) 程序的需要即時(shí)分配,且分配的大小就是程序要求的大小。
當(dāng)程序運(yùn)行到需要一個(gè)動(dòng)態(tài)分配的變量或?qū)ο髸r(shí),必須向系統(tǒng)申請(qǐng)取得堆中的一塊所需大小的存貯空間,用于存貯該變量或?qū)ο蟆.?dāng)不再使用該變量或?qū)ο髸r(shí),也就是它的生命結(jié)束時(shí),要顯式釋放它所占用的存貯空間,這樣系統(tǒng)就能對(duì)該堆空間進(jìn)行再次分配,做到重復(fù)使用有限的資源。
在使用數(shù)組的時(shí)候,總有一個(gè)問(wèn)題困擾著我們:數(shù)組應(yīng)該有多大?在很多的情況下,你并不能確定要使用多大的數(shù)組,比如上例,你可能并不知道我們要定義的這個(gè)數(shù)組到底有多大,那么你就要把數(shù)組定義得足夠大。這樣,你的程序在運(yùn)行時(shí)就申請(qǐng)了固定大小的你認(rèn)為足夠大的內(nèi)存空間。
即使你知道你想利用的空間大小,但是如果因?yàn)槟撤N特殊原因空間利用的大小有增加或者減少,你又必須重新去修改程序,擴(kuò)大數(shù)組的存儲(chǔ)范圍。這種分配固定大小的內(nèi)存分配方法稱(chēng)之為靜態(tài)內(nèi)存分配。但是這種內(nèi)存分配的方法存在比較嚴(yán)重的缺陷,特別是處理某些問(wèn)題時(shí):在大多數(shù)情況下會(huì)浪費(fèi)大量的內(nèi)存空間,在少數(shù)情況下,當(dāng)你定義的數(shù)組不夠大時(shí),可能引起下標(biāo)越界錯(cuò)誤,甚至導(dǎo)致嚴(yán)重后果。
我們用動(dòng)態(tài)內(nèi)存分配就可以解決上面的問(wèn)題. 所謂動(dòng)態(tài)內(nèi)存分配就是指在程序執(zhí)行的過(guò)程中動(dòng)態(tài)地分配或者回收存儲(chǔ)空間的分配內(nèi)存的方法。動(dòng)態(tài)內(nèi)存分配不象數(shù)組等靜態(tài)內(nèi)存分配方法那樣需要預(yù)先分配存儲(chǔ)空間,而是由系統(tǒng)根據(jù)程序的需要即時(shí)分配,且分配的大小就是程序要求的大小。
從以上動(dòng)、靜態(tài)內(nèi)存分配比較可以知道動(dòng)態(tài)內(nèi)存分配相對(duì)于靜態(tài)內(nèi)存分配的特點(diǎn):
1、不需要預(yù)先分配存儲(chǔ)空間;
2、分配的空間可以根據(jù)程序的需要擴(kuò)大或縮小。
常見(jiàn)的動(dòng)態(tài)內(nèi)存錯(cuò)誤:
(1)對(duì)NULL指針進(jìn)行解引用操作
(2)對(duì)分配的內(nèi)存進(jìn)行操作時(shí)越過(guò)邊界
(3)釋放并非動(dòng)態(tài)分配的內(nèi)存
(4)試圖釋放一塊動(dòng)態(tài)分配的內(nèi)存的一部分以及一塊內(nèi)存被釋放之后被繼續(xù)使用。
說(shuō)明:
1、動(dòng)態(tài)分配最常見(jiàn)的錯(cuò)誤就是忘記檢查所請(qǐng)求的內(nèi)存是否成功分配。
2、動(dòng)態(tài)內(nèi)存分配的第二大錯(cuò)誤來(lái)源是操作內(nèi)存時(shí)超出了分配內(nèi)存的邊界。
當(dāng)你使用free時(shí),可能出現(xiàn)各種不同的錯(cuò)誤:
1、傳遞給free的指針必須是一個(gè)從malloc、calloc或realloc函數(shù)返回的指針。
2、傳遞給free函數(shù)一個(gè)指針,讓它釋放一塊并非動(dòng)態(tài)分配的內(nèi)存可能導(dǎo)致程序立即終止或在晚些時(shí)候終止。
3、試圖釋放一塊動(dòng)態(tài)分配內(nèi)存的一部分也有可能引起類(lèi)似問(wèn)題。
?
//實(shí)例:動(dòng)態(tài)內(nèi)存分配實(shí)現(xiàn)可變長(zhǎng)一維數(shù)組 #define _GRT_SECURE_NO_WARNNGS #include#include #include"array.h"http://這個(gè)頭文件 里邊包含一個(gè)結(jié)構(gòu)表示數(shù)組和下列函數(shù)的聲明原型 const Block_size = 20;///一次增容20個(gè)存儲(chǔ)空間 /* Array array_creat(int ints_size); //創(chuàng)建一個(gè)數(shù)組 void array_free(Array *a);//回收空間 int array_size(const Array *a);//查看當(dāng)前數(shù)組大小 int *array_at(Array *a, int index);//訪(fǎng)問(wèn)數(shù)組 void array_inlate(Array *a, int more_size);//增容 */ int main(void) { Array a;//表示數(shù)組初始值的大小 int i, j,n,m=0; while (1) { printf("請(qǐng)輸入你需要多大的數(shù)組: "); scanf("%d", &n); a = array_creat(n);//這個(gè)可得到a里邊返回的參數(shù) printf("輸入數(shù)據(jù) "); for (i = 0; i < n; i++) { scanf("%d", &j); *array_at(&a, i) = j;//這個(gè)函數(shù)相當(dāng)與是數(shù)組 把j的值保存到數(shù)組里邊的元素中去 } printf("輸出數(shù)據(jù): "); for (i = 0; i < n; i++) {//遍歷輸出 printf("%d ", a.arrray[i]); printf(" "); } printf(" "); printf("輸入1初始化數(shù)組大小,輸入其他表示退出程序: "); scanf("%d", &n); if (n == 1) { m = 0;//清零 j = 0; array_free(&a);//釋放之前的內(nèi)存 } else { exit(0);//退出程序 } } return 0; } Array array_creat(int ints_size) //創(chuàng)建一個(gè)數(shù)組 { Array a;//定義一個(gè)數(shù)組的結(jié)構(gòu)體 a.size=ints_size; //表示數(shù)組的長(zhǎng)度 a.arrray = (int *)malloc(sizeof(int)*a.size);//前一個(gè)int*是強(qiáng)制類(lèi)型轉(zhuǎn)換,后面的表示一個(gè)int 是4個(gè)字節(jié) 總共就是長(zhǎng)度乘以 return a;//返回的作用是 讓主函數(shù)調(diào)用它時(shí),能夠得到它的參數(shù) } void array_free(Array *a)//回收空間 { free(a->arrray); a->arrray = NULL;//讓指針指空 不至于成為野指針 a->size = 0; } //封裝 int array_size(const Array *a)//查看當(dāng)前數(shù)組大小 { return a->size; } int *array_at(Array *a, int index)//訪(fǎng)問(wèn)數(shù)組 { if (index >= a->size) { //下面的公式是為了算出Block_size的底在哪 //比如130,如果直接加20要加兩次,但是用公式就一次完成 array_inlate(a, (index / Block_size + 1)*Block_size - a->size);//在原來(lái)的基礎(chǔ)上加20個(gè) } //返回指針 加括號(hào)是為了保持優(yōu)先級(jí)不出錯(cuò) return &(a->arrray[index]); //如果返回的是值,那將不能被改變,返回指針就可以進(jìn)行操作了 } void array_inlate(Array *a, int more_size)//增容 { int *p = (int*)malloc(sizeof(int)*(a->size+more_size));//重新申請(qǐng)一塊更大的內(nèi)存 100 +20 int i; for (i = 0; i < a->size; i++) {//把之前數(shù)組的內(nèi)容拷貝到新的數(shù)組中去 p[i] = a->arrray[i]; } free(a->arrray);//把之前的數(shù)組釋放 a->arrray = p;//將指針改變指向 重定向 a->size += more_size;//大小加上新增的 } /*程序演示: 請(qǐng)輸入你需要多大的數(shù)組: 5 輸入數(shù)據(jù) 1 2 3 4 5 輸出數(shù)據(jù): 1 2 3 4 5 輸入1初始化數(shù)組大小,輸入其他表述退出程序: 1 請(qǐng)輸入你需要多大的數(shù)組: 6 輸入數(shù)據(jù) 1 2 3 4 5 6 輸出數(shù)據(jù): 1 2 3 4 5 6 輸入1初始化數(shù)組大小,輸入其他表述退出程序: 0 進(jìn)程1520已退出.返回代碼為 0. 按任意鍵關(guān)閉此窗口...
//實(shí)例:動(dòng)態(tài)內(nèi)存分配實(shí)現(xiàn)可變長(zhǎng)二維數(shù)組 #include#include int main(void) { int n, m; scanf("%d %d", &n, &m);//n=5 m=2 按照自己輸入 來(lái)確定二維數(shù)組的大小 int **p = (int **)malloc(sizeof(int *) * n);//利用二級(jí)指針 申請(qǐng)五行元素 //p是一個(gè)二級(jí)指針 malloc函數(shù)返回一個(gè)int* 的類(lèi)型 sizeof(int*)表示乘以的指針類(lèi)型的大小 /*、申請(qǐng)m個(gè)能夠能夠存放 int* 類(lèi)型的空間,并將首地址返回給一個(gè)二維指針p; 內(nèi)存可能的分布情況: int a < -- int *; < -- int **p; int b < -- int *; int c < -- int *; int d < -- int *; */ // (int **) 一個(gè)*表示強(qiáng)制類(lèi)型轉(zhuǎn)換,另一個(gè)表示指針 int * //sizeof(int*),不能少*,一個(gè)指針的內(nèi)存大小,每個(gè)元素是一個(gè)指針。用指針長(zhǎng)度乘以數(shù)量 (int*)*n // 這個(gè)p指針的數(shù)據(jù)類(lèi)型是個(gè)二級(jí)指針,它指向的這個(gè)空間里放的是些一級(jí)指針 for (int i = 0; i < 5; i++)//每行有兩列元素 { p[i] = (int *)malloc(sizeof(int) * m);//每個(gè)元素是int大小 4*m 將元素保存到每一行 //每一個(gè)一級(jí)指針值的大小 指向一個(gè)實(shí)際大小的空間 // *(p+i) = p[i] 每一次移動(dòng)表示行的移動(dòng) } //賦值 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { p[i][j] =1; //*(*(p + i) + j) = p[i][j] } } for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { //輸出數(shù)組每個(gè)元素值和地址 printf("%d=%p ", p[i][j],&p[i][j]); } printf(" "); } for (int i = 0; i < n; i++) {//按 行 釋放指針 free(p[i]); } free(p);//釋放整體 return 0; } /*程序演示: 5 2 1=010F44C0 1=010F44C4 1=010F4378 1=010F433C 1=010F4330 1=010F4374 1=010FAB60 1=010FAB64 1=010FAD98 1=010FAB94 進(jìn)程8432已退出.返回代碼為 0. 按任意鍵關(guān)閉此窗口...
?
const 函數(shù)(補(bǔ)充)
? ? ? 之前一直把這個(gè)關(guān)鍵字漏掉了現(xiàn)在補(bǔ)上,const 限定符,它把一個(gè)對(duì)象轉(zhuǎn)換成一個(gè)常量,C語(yǔ)言中const關(guān)鍵字是constant的縮寫(xiě),通常翻譯為常量、常數(shù)等,有些朋友一看到const關(guān)鍵字馬上就想到了常量。事實(shí)上在C語(yǔ)言中const功能很強(qiáng)大,它可以修飾變量、數(shù)組、指針、函數(shù)參數(shù)等。
1、修飾變量:
在程序中使用const修飾變量,就可以對(duì)變量聲明為只讀特性,并保護(hù)變量值以防被修改。如下:
const int i = 5;? 變量i具有只讀特性,不能夠被更改;若想對(duì)i重新賦值,如i = 10;則是錯(cuò)誤的。
值得注意的是,定義變量的同時(shí),必須初始化。定義形式也可以寫(xiě)成int const i=5,同樣正確。
此外,const修飾變量還起到了節(jié)約空間的目的,通常編譯器并不給普通const只讀變量分配空間,而是將它們保存到符號(hào)表中,無(wú)需讀寫(xiě)內(nèi)存操作,程序執(zhí)行效率也會(huì)提高。
2、修飾數(shù)組
C語(yǔ)言中const還可以修飾數(shù)組,舉例如下:
const int array[5] = {1,2,3,4,5};
array[0] = array[0]+1; //錯(cuò)誤
數(shù)組元素與變量類(lèi)似,具有只讀屬性,不能被更改;一旦更改,如程序?qū)?huì)報(bào)錯(cuò)。
3、修飾函數(shù)參數(shù)
const關(guān)鍵字修飾函數(shù)參數(shù),對(duì)參數(shù)起限定作用,防止其在函數(shù)內(nèi)部被修改。所限定的函數(shù)參數(shù)可以是普通變量,也可以是指針變量。舉例如下:
void fun1(const int i)
i++; //對(duì)i的值進(jìn)行了修改,程序報(bào)錯(cuò)
void fun2(const int *p)
(*p)++; //對(duì)p指向空間的值進(jìn)行了修改,程序報(bào)錯(cuò)
保護(hù)數(shù)組中的元素:
為了避免函數(shù)的意圖不是為了修改數(shù)組當(dāng)中的數(shù)據(jù)內(nèi)容,那么在函數(shù)原始聲明定義中時(shí)應(yīng)使用關(guān)鍵字const,如:
int sum(const a[ ],int n);? 這段代碼告訴編譯器,該函數(shù)不能修改a所指向的數(shù)組中的內(nèi)容,如果在函數(shù)中不小心使用類(lèi)似a[i]++;的表達(dá)式,那么程序?qū)?huì)報(bào)錯(cuò)。
要注意的是,這樣使用const并不是要求原數(shù)組是常量,而是該函數(shù)在處理數(shù)組時(shí)將其視為常量,不可修改,這樣使用const可以保護(hù)數(shù)組當(dāng)中的數(shù)據(jù)不被修改。
4、修飾指針
C語(yǔ)言中const修飾指針要特別注意,共有兩種形式,一種是用來(lái)限定指向空間的值不能修改;另一種是限定指針不可更改。舉例說(shuō)明如下:
int i = 5;
int j = 6;
int k = 7;
const int * p1 = &i; //定義1
int * const p2 =&j; //定義2
上面定義了兩個(gè)指針p1和p2。
在定義1中const限定的是 * p1,即其指向空間的值不可改變,若改變其指向空間的值如*p1=20,則程序會(huì)報(bào)錯(cuò);但p1的值是可以改變的,對(duì)p1重新賦值如p1=&k是沒(méi)有任何問(wèn)題的。
在定義2中const限定的是指針p2,若改變p2的值如p2=&k,程序?qū)?huì)報(bào)錯(cuò);但*p2,即其所指向空間的值可以改變,如 * p2=80是沒(méi)有問(wèn)題的,程序正常執(zhí)行。
關(guān)于指針賦值和const需要注意一些規(guī)則:
1、把const數(shù)據(jù)或非const數(shù)據(jù)的地址初始化為指向const的指針或?yàn)槠滟x值是合法的
2、可以聲明并初始化一個(gè)不能指向別處的指針,關(guān)鍵是const的位置,這時(shí)候,這個(gè)指針可以修改它所指向的值,但是只能指向初始化時(shí)設(shè)置的地址。
3、在創(chuàng)建指針時(shí)還可以使用兩次const,表示該指針既不能修改它所指向的地址,也不能修改它所指向地址上的值
清單:
?
int a[10]; const double b[10]; const double *p=a; //有效 p=b; //有效 p=&a[3]; //有效 --------------------------- int a[10]; const double b[10]; //只能將非const數(shù)據(jù)的地址賦給普通指針 (否則,通過(guò)指針就能修改const數(shù)組中的值了) double *p=a //有效 p=b; //無(wú)效* p=&a[3]; //有效 --------------------------- void sum(const double *a,int n); //此函數(shù)可以接受普通數(shù)組和const數(shù)組名作為參數(shù),因?yàn)檫@兩種參數(shù)都可以用來(lái)初始化指向const的指針 int a[10]; const double b[10]; sum(a,5);//合法 sum(b,4);//合法 --------------------------- int a[10]; double *const p=a; //p指向數(shù)組的開(kāi)始 p=&a[0]; //不允許,因?yàn)樵撝羔槻荒苤赶騽e處 *p=9.9; //可以做,更改a[0]的值 --------------------------- int a[10]; const double *const p=a; p=&a[0]; //不允許 *p=9.9; //不允許
?
塊
塊指的是一塊數(shù)據(jù),是個(gè)抽象的概念,和C語(yǔ)言沒(méi)有關(guān)系,這種抽象的東西,別說(shuō)其他語(yǔ)言也能用,就是日常生活中也會(huì)把東西分塊管理,C語(yǔ)言中沒(méi)有對(duì)塊進(jìn)行定義,因?yàn)檫@只是個(gè)抽象的概念,塊可以是內(nèi)存塊,數(shù)據(jù)塊,程序塊,哪怕是豆腐塊也能是塊...? 意思就是在管理中被劃分為一類(lèi)的一個(gè)基本單位
存儲(chǔ)期:
存儲(chǔ)期這也是變量的特點(diǎn),它稱(chēng)為生存期,表示變量在內(nèi)存中存在的時(shí)間的長(zhǎng)短。
? 1、靜態(tài)存儲(chǔ)期:在程序編譯時(shí)就分配內(nèi)存空間并保持不變,程序執(zhí)行結(jié)束后才釋放。
? 2、線(xiàn)程存儲(chǔ)期:thread_local,其聲明后會(huì)給每個(gè)線(xiàn)程分配一個(gè)單獨(dú)的私有備份
? 3、自動(dòng)存儲(chǔ)期:局部變量通常都自動(dòng)為auto 存儲(chǔ)期
? 4、動(dòng)態(tài)存儲(chǔ)期:就是用new 或者malloc分配的內(nèi)存,如果不主動(dòng)釋放,在整個(gè)程序都占有內(nèi)存
作用域:
這個(gè)是表示變量在哪些范圍內(nèi)起作用,由鏈接點(diǎn)決定。
1、塊作用域:用{}括起來(lái)的,從聲明開(kāi)始到“}” 結(jié)束
? ? 2、函數(shù)作用域:goto(標(biāo)識(shí)符) 的作用域?yàn)檎麄€(gè)函數(shù)。
? ? 3、函數(shù)原型作用域:函數(shù)聲明開(kāi)始,函數(shù)聲明結(jié)束而結(jié)束
? ? 4、文件作用域:整個(gè)文件或者程序
鏈接屬性:表示變量能在哪些范圍內(nèi)使用.
1、內(nèi)部鏈接 :只能在源文件內(nèi)部使用.
2、外部鏈接 : 能在源文件內(nèi)部和外部文件中使用.
3、空連接 : 只能在代碼塊內(nèi)(函數(shù)內(nèi)部)使用.
限定符:
? volatile:
? const:
? restrict:
_Atomic:
審核編輯:湯梓紅
評(píng)論