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