本文部分內容參考了C Primer Plus(sixth edition)一書 存儲類別和記憶體分佈 簡單介紹一下變數的存儲類別和它的記憶體分佈,我們先通過一張表來瞭解一些基本術語: 〉〉塊指的是一對花括弧括起來的代碼。C99之後,塊也可以是迴圈語句+迴圈體(單一語句)。具有塊作用域的變數只能在塊內可見 ...
本文部分內容參考了C Primer Plus(sixth edition)一書
存儲類別和記憶體分佈
簡單介紹一下變數的存儲類別和它的記憶體分佈,我們先通過一張表來瞭解一些基本術語:
術語 | 解釋 | 舉例 |
作用域 | 描述程式中可訪問的標識符的區域 | 塊作用域,函數作用域,函數原型作用域,文件作用域 |
鏈接 | 變數屬於哪一部分私有 | 外部鏈接(全局可見),內部鏈接(單一文件可見),無鏈接(塊內可見) |
存儲期 | 描述通過標識符訪問對象的生命期 | 靜態存儲期,線程存儲期,自動存儲期,動態分配存儲期 |
翻譯單元 | 一個源文件包含多個頭文件,編譯時當作一個翻譯單元,作一個文件 | 頭文件包含在源代碼文件內 |
〉〉塊指的是一對花括弧括起來的代碼。C99之後,塊也可以是迴圈語句+迴圈體(單一語句)。具有塊作用域的變數只能在塊內可見。C99之前,變數只允許聲明在塊的開頭。無鏈接。
〉〉函數作用域僅用於goto標簽,一個標簽首次出現在函數的內層塊中,它的作用域將延伸直整個函數。無鏈接。
〉〉函數原型作用域:聲明函數原型時形參的作用域,僅限於原型。無鏈接。註意,如果在某一函數int fun的原型中聲明變長數組,應該這樣聲明int fun(int n, int m, int vla[n][m]);
〉〉文件作用域:定義在函數外,從定義處到文件末尾均可見。具有內部和外部鏈接。
〉〉外部鏈接:具有外部鏈接的變數在程式的所有文件中都可見。例如,main.c中定義了一個外部鏈接變數int var;在lock.c中就要用extern int x;來聲明使用。
〉〉內部鏈接:具有該鏈接的變數只在該文件可見。例如:static int var; var使用時不必加上像外部鏈接變數的聲明。
〉〉無鏈接:不具有文件作用域的變數無鏈接。
〉〉靜態存儲期:生命(在記憶體中存在時間):程式開始到結束。
〉〉線程存儲期:生命:從線程開始到結束。
〉〉自動存儲期:生命以塊為參照,離開塊即消失。
〉〉動態分配存儲期:由程式員管理。
總結表如下:
存儲類別 | 存儲期 | 作用域 | 鏈接 | 聲明方式 |
自動 | 自動 | 塊 | 無 | 塊內 |
靜態外部鏈接 | 靜態 | 文件(整個程式) | 外部 | 所有函數外,在另一文件使用前需要用extern再次聲明 |
靜態內部鏈接 | 靜態 | 文件(單一翻譯單元) | 內部 | 所有函數外,用static聲明 |
靜態無鏈接 | 靜態 | 塊 | 無 | 塊內,用static聲明 |
此外還有寄存器變數,由於這種變數實用性不高,不討論。
在記憶體中,靜態數據(包括常量)占用一個區域,自動數據占用另一個區域,動態分配的數據占第三個區域(記憶體堆)。
動態記憶體分配函數malloc()和釋放函數free()
在C99標準發佈之前,我們不能使用變數作為聲明數組的元素個數。如果我們要創建一個自定義長度的數組,就不能使用原來定義普通靜態數組的方法。那麼該怎麼辦呢?這時候就要通過動態分配記憶體來聲明動態數組了。
malloc()函數原型在stdlib.h文件中。它返回一個指向void類型的指針,這個指針指向分配記憶體的首地址。下麵這段代碼分配一個具有10個int類型元素的動態數組,用指針ptr指向這個數組的首元素。
int *ptr, n = 10; ptr = (int *)malloc( (size_t)n * sizeof(int) );
malloc()的括弧里應填入所需要分配的記憶體大小,以位元組為單位,類型是size_t。為了提供程式的可移植性,我們用了sizeof()運算符來獲取當前系統int類型的變數所占用的位元組大小。建議大家使用強制類型轉換來使用指向void類型的指針。
但是,如果分配記憶體失敗了,會怎麼樣?malloc()函數會返回一個指向NULL的指針,如果程式在記憶體分配失敗後繼續使用記憶體,就會導致程式異常終止。所以,在分配記憶體之後,需要檢查一下成功分配了沒有。
int *ptr, n = 10; ptr = (int *)malloc( (size_t)n * sizeof(int) ); if(ptr == NULL) exit(EXIT_FAILURE);
或者:
int *ptr, n = 10; if( !( ptr = (int *)malloc( (size_t)n * sizeof(int)) ) ) exit(EXIT_FAILURE);
都是很好的方法。
我們知道,malloc()分配的記憶體具有動態分配存儲期,由程式員控制變數的生命期。如果程式結束前沒有及時地釋放它,程式結束之後就仍然存在。更糟糕的是,如果程式運行中,指向該塊記憶體首地址的指針全部被銷毀,那麼這塊記憶體就永遠無法正確訪問,也就無法釋放這塊記憶體,這就會導致記憶體泄露。記憶體泄露問題很嚴重,會導致系統停止運行!因為沒有空餘的記憶體空間,所以,在分配記憶體之後如果不需要再次使用,一定要釋放!警惕不小心更改指向動態分配記憶體空間的指針!釋放動態分配的記憶體,我們用free()函數。
free(ptr);
這行代碼就釋放了之前用malloc()分配的記憶體空間。
動態數組排序實例
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <conio.h> 4 5 int main(int argc, char * argv[]){ 6 int * ptr; 7 int key, size, i, j; 8 9 printf("請輸入動態數組大小:"); 10 while( !( scanf("%d",&size) ) || size < 0 ){ 11 printf("輸入錯誤,請重新輸入。\n:"); 12 while( getchar() != '\n' );//調整輸入緩衝區 13 } 14 15 if( !( ptr = (int *)malloc( (size_t)size * sizeof(int)) ) ){//分配記憶體 16 printf("記憶體請求失敗!"); 17 exit(EXIT_FAILURE); 18 } 19 20 printf("請依次輸入數組元素內容:\n"); 21 for(i = 0; i < size; i++) 22 scanf("%d",&ptr[i]); 23 while( getchar() != '\n' ); 24 25 for(i = 1; i < size; i++){//插入排序 26 j = i - 1; 27 key = ptr[i]; 28 while( j >= 0 && ptr[j] > key){ 29 ptr[j + 1] = ptr[j]; 30 j--; 31 } 32 ptr[j + 1] = key; 33 } 34 35 printf("各元素排序後輸出如下:"); 36 for(i = 0; i < size; i++) 37 printf("%d ",ptr[i]); 38 39 free(ptr); //釋放記憶體,重中之重 40 41 _getch(); 42 return 0; 43 }動態數組排序實例
運行結果:
補充:realloc()函數
原型:void *realloc(void * ptr, size_t size);
簡單描述:把ptr指向的記憶體空間更改為size位元組,size位元組內的記憶體塊內容不變。該函數返回塊的位置。如果不能重新分配空間,則返回NULL。如果ptr為NULL,其行為與調用帶size參數的malloc()相同。如果size為0且ptr不是NULL,其行為與調用帶ptr參數的free相同。
不推薦大量使用這個函數,因為當不能重新分配空間時,函數返回NULL,原來已分配的記憶體塊地址丟失,會造成記憶體泄露。儘管在很多時候這個函數非常方便。如果你需要使用這個函數,請先保存原來記憶體塊的地址!