理解函數和指針的結合使用,需要理解程序棧。大部分現代的塊結構語言,比如C,都用到了程序棧來支持函數的運行。調用函數時,會創建函數的棧幀並將其推到程序棧上。函數返回時,其棧幀從程序棧上彈出。
在使用函數時,有兩種情況指針很有用。一種是將指針作為參數傳遞給函數,函數可以修改指針所引用的數據,可以高效的傳遞大塊數據。另一種是聲明函數指針。
程序的棧和堆是C程序的重要運行時元素。程序棧是支持函數執行的內存區域,通常和堆共享一塊內存區域。通常程序棧在區域的下部,堆在上部。程序棧存放棧幀(stack frame),棧幀有時候也稱為活躍記錄或活躍幀。棧幀存放函數參數和局部變量。堆則管理動態內存。
調用函數時,函數的棧幀被推到棧上,棧向上“長出”一個棧幀。當函數終止時,其棧幀從程序上彈出。棧幀所使用的內存不會被清理,但有可能被另一個棧幀覆蓋。
棧幀由以下幾種元素組成:
* 返回地址。函數完成後要返回的程序內部地址。
* 局部數據存儲。為局部變量分配的內存。
* 參數存儲。為函數參數分配的內存。
* 棧指針和基指針。運行時系統用來管理棧的指針。
棧指針通常指向棧頂部。基指針(幀指針)通常存在並指向棧幀內部的地址,比如返回地址,用來協助訪問棧幀內部的元素。這兩個指針都是運行時系統用來管理程序棧的地址。系統在創建棧幀時,將參數以跟聲明時相反的順序推到幀上,最後推入局部變量。C把塊語句當做“微型”函數,會在適當的時候將其推入棧和從棧上彈出。
參數和局部變量的精確地址可能會變化,不過順序一般不變。這一點可以解釋參數和變量分配內存的相對順序。將棧幀推到程序棧上時,系統可能會好近內存,這種情況稱為棧溢出。要記住每個線程通常都有自己的程序棧,一個或多個線程訪問內存中的同一個對象可能會導致沖突。
傳遞參數(包括指針)的時候,傳遞的是它們的值的副本。當涉及大型數據結構時,傳遞參數的指針會更高效。傳遞對象的指針意味著不需要復制對象,但可以通過指針訪問對象。
用指針來傳遞數據的一個主要原因是函數可以修改數據。如果不希望函數修改數據,可以傳遞指向常量的指針。
從函數返回指針有兩種情況:一種是在函數內部為指針分配內存並返回指向內存的指針,另一種是函數的調用者負責指針內存的分配和釋放。從函數返回指針可能存在幾個潛在的問題:
* 返回未初始化的指針。
* 返回指向無效地址的指針。
* 返回局部變量的指針。
* 返回指針但是沒有釋放內存。
設想一下在函數中聲明一個指針並為該指針分配內存,但是在調用該函數的時候沒有使用一個指針變量來接受函數的返回值,因此我們丟失了該分配內存的地址,導致內存洩露。
局部數據指針是指當函數返回的時候,(非動態分配的)局部數據的地址也就無效了。動態分配的內存與此不同,存在於堆上,即使函數返回也不受影響。當變量為static,就會在棧幀外部為其分配內存,每次調用函數都會訪問同一塊內存。
在將指針作為參數傳遞時,在使用之前判斷指針是否為NULL是個好習慣。重復free一個指針會引發錯誤,在free之前也應判斷指針是否為NULL。而且在釋放之後將指針置為NULL。
傳遞指針將允許我們修改指針指向的數據,如果我們想修改的是指針怎麼辦?當然是傳遞指向指針的指針了。
函數指針是持有函數地址的指針。通過將函數指針作為參數傳遞,使我們避免將事件處理程序硬編碼進函數裡,更加靈活。
void (*foo)();
void是返回類型,foo是指針變量名,後邊的括號裡是參數。
int(*fptr1)(int); int square(int num) { return num*num; } fptr1 = square; int n = 5; printf(" result is %d\n", fptr1(n));
使用函數的名字直接給函數指針賦值,它會把函數的地址賦給指針。也可以對函數名字使用取地址操作符,但是意思是一樣的,在此處上下文中會忽略取地址操作符。可以為函數指針聲明一個類型定義,通過類型定義來聲明函數指針。
typedef int (*funcptr)(int); funcptr ptr2; ptr2 = square;
傳遞函數指針只需把函數指針聲明為參數即可。
funcptr ptr2; ptr2 = square; int compute(funcptr squ, int num) { return squ(num); } compute(ptr2,n);
返回函數指針需要把函數的返回類型聲明為函數指針。函數指針數組可以基於某些條件選擇要執行的函數,聲明這種數組只要把函數指針聲明為數組的類型即可。
typedef int (*operation)(int,int); operation operations[128] = {NULL};//使用NULL來初始化所有數組元素
我們可以用相等和不等操作符來比較函數指針。不同的函數指針的長度不一定相等。雖然可以將一種函數指針轉換為另一種函數指針,但是應該謹慎使用這種方法,因為運行時系統不會驗證函數指針所用的參數是否正確。不應該把函數指針轉換成void*指針。基本指針用作占位符,用來交換函數指針的值。一定要確保給函數指針傳遞的參數是正確的,否則會造成不確定行為。
總體來說,函數指針允許應用程序根據不同的條件執行不同的函數,對於控制應用程序內的執行序列很有用。