本文部分內容參考了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聲明
此外還有寄存器變量,由於這種變量實用性不高,不討論。
在內存中,靜態數據(包括常量)占用一個區域,自動數據占用另一個區域,動態分配的數據占第三個區域(內存堆)。
在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()分配的內存空間。
運行結果:
原型:void *realloc(void * ptr, size_t size);
簡單描述:把ptr指向的內存空間更改為size字節,size字節內的內存塊內容不變。該函數返回塊的位置。如果不能重新分配空間,則返回NULL。如果ptr為NULL,其行為與調用帶size參數的malloc()相同。如果size為0且ptr不是NULL,其行為與調用帶ptr參數的free相同。
不推薦大量使用這個函數,因為當不能重新分配空間時,函數返回NULL,原來已分配的內存塊地址丟失,會造成內存洩露。盡管在很多時候這個函數非常方便。如果你需要使用這個函數,請先保存原來內存塊的地址!