C pointer to function 函式指標 學習心得
這是函式指標學習心得的第一篇,在這一篇中你會學習到:
1. 函式在記憶體中的情況
2. 如何宣告一個函式指標
3. 函式的宣告
4. 使用 typedef 來定義一個函式指標的類型
本篇著重在介紹函式指標,實際應用方面會在下一篇介紹
------------------------ 1. 函式在記憶體中的情況 ------------------------
在真正的開始學習函式指標 ( pointer to function ) 之前,我們要先弄懂到底什麼是函式以及函式在記憶體分布的情形。
當一個程式碼檔案 ( .c , .cpp ...) 被執行的時候,會產生出一個程序 ( process ),此時才會開始佔用記憶體及進行各種運算。先前我們定義的指標都是針對一個資料型態,如:int * 這是一個指向 int 資料類型的指標。然而,C為什麼沒有為 function 也定義出一個資料型別?因為不同的 function 依照建立的方式不同而有不同的 type ,所以沒辦法替每一種 function 都定義出一個通用的型別。
實際上,function 和一般的資料儲存的狀況不太一樣,他儲存的是一堆的指令。
我們可以參考下面的圖片:
圖片來源:http://ccckmit.wikidot.com/cp%3Aenvironment
當一個 process 產生的時候,會有一塊自己可以使用的記憶體,並且這個記憶體分成許多不同的區塊,儲存不同的東西。一般來說,我們使用的區域變數是儲存在 stack 區段。程式碼則是儲存在 .text 區。因此,雖然和一般的資料型式不同,但是程式碼也是會佔用記憶體的!理所當然,他也會有自己的位址。
我們可以利用反組譯的方式來查看:
編譯方式:gcc -g -o test test.c
在這裡我使用 gdb 來輔助
第 1 行 顯示 function name 的位址,接著我們到第15行可以發現,function 第 1 行指令的位址和function name的位址是一樣的,所以我們可以得知:其實function name的位址就是function第一行指令的位址。接著,在第 8 行中,我們可以看到main呼叫函式也是利用函式第 1 行指令的位址。
由此可知,其實在function在記憶體中也是有特定儲存的位址。
如果我們在linux的環境中使用 objdump 這個指令可以看得更清楚,他會指名在.text區
Disassembly of section .text:
.....
.....
000000000040052d <say_hello>:
------------------------ 2. 如何宣告一個函式指標 ------------------------
宣告方式: return_type (*func_pointer)( parameter list );
例如:
第8行宣告一個指標名為 fptr ,指向 int (int, int)
換句話說,fptr的type : int (*)(int, int)
我們可以用以下兩種方式將已知的function, assign 給 function pointer:
1. fptr = &func_name;
2. fptr = func_name;
使用的時候也有兩種方法可以使用:
1. (*fptr)(num1, num2);
2. fptr(num1, num2);
每次讀到這一段的時候,總是有個疑惑:為什麼兩種方法都可以?
甚至,你編譯下面這一段 code 也可以執行:
Why?
要確保初值化(initialization)或是assignment的正確性,取決於 1.數值 2.型別
舉例來說:
int func(int, int);
int (*fptr)(int, int);
type of fptr is : int (*)(int, int);
type of func is : int (int, int);
type if &func is : int (*)(int , int);
所以 fptr = &func 很合理,型別正確並且數值也正確(&func數值和func一樣)
可是 fptr = func 型別不一樣。 所以在這裡其實做了implicit conversion
將function name ( i.e. function designator )轉成函式指標使用
至於,使用的方式有兩種一種用deference(i.e use * operator),一種不用
我曾經在一本書上看到這樣的解釋:
在使用function的時候,fun(),其中()稱作 function-call operator
function-call operator 只允許 pointer to function使用。
所以一般我們在使用func_name(); 其實會做implicit conversion將func_name轉型
所以我們這樣寫其實也可以執行(&func_name)();
以上就是 function pointer 的基本操作。
------------------------ 3. 函式的宣告 ------------------------
不知道大家有沒有想過一個問題:
我們在宣告變數的時候,都是依循這樣的形式 : type var_name;
為什麼宣告function是 : return_type func_name( parameter list );
其實我們的看法應該是這樣 func_name( parameter list) 這一整個和var_name對照
所以,我們在gdb中檢查 func_name( parameter list) 這整個東西的type會和return type是一樣的。
在gdb中要查看變數的type可以使用:ptype var_name / whatis var_name
利用先前的例子:
(gdb) ptype say_hello
type = void ()
(gdb) ptype say_hello()
type = void
(gdb) ptype &say_hello
type = void (*)()
------------------- 4. 使用 typedef 來定義一個函式指標的類型 ---------------
每次我們要宣告一個 function pointer 假如都要照之前那樣寫,對大多數人來說其實不太容易看。更甚者,牽扯到一堆轉型的時候更讓人頭暈目眩。
因此我們利用typedef來定義一個function pointer的type
int (*ptr)(int, int); // declare a pointer to function : ptr
// and its type is : int (*)(int, int)
typedef int (*func_t)(int, int);
這個時候,func_t 就是一個 int(*)(int, int)的型別了
這個語法可能讓人感到confuse,因為以前我們定義的方式很單純:
typedef int bool;
在這裡我會這樣看 (*func_t)(int, int)是一個東西,然後藉由
typedef int (*func_t)(int, int);來間接定義func_t
所以,以後我們就可以這樣寫了:
謝謝觀看,如果有寫的不清楚或是有謬誤的地方還請各界先賢不吝賜教
感謝!
reference :
1. http://www.newty.de/fpt/index.html
2. linux C 一站式編程
3. google 大神