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 區。因此,雖然和一般的資料型式不同,但是程式碼也是會佔用記憶體的!理所當然,他也會有自己的位址。
我們可以利用反組譯的方式來查看:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<stdio.h> | |
void say_hello(); | |
void say_hello(){ | |
printf("Hello\n"); | |
return; | |
} | |
int main(){ | |
say_hello(); | |
return 0; | |
} |
編譯方式:gcc -g -o test test.c
在這裡我使用 gdb 來輔助
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(gdb) print say_hello | |
$1 = {void ()} 0x40052d <say_hello> | |
(gdb) disassemble main | |
Dump of assembler code for function main: | |
0x000000000040053e <+0>: push %rbp | |
0x000000000040053f <+1>: mov %rsp,%rbp | |
0x0000000000400542 <+4>: mov $0x0,%eax | |
0x0000000000400547 <+9>: callq 0x40052d <say_hello> | |
0x000000000040054c <+14>: mov $0x0,%eax | |
0x0000000000400551 <+19>: pop %rbp | |
0x0000000000400552 <+20>: retq | |
End of assembler dump. | |
(gdb) disassemble say_hello | |
Dump of assembler code for function say_hello: | |
0x000000000040052d <+0>: push %rbp | |
0x000000000040052e <+1>: mov %rsp,%rbp | |
0x0000000000400531 <+4>: mov $0x4005e4,%edi | |
0x0000000000400536 <+9>: callq 0x400410 <puts@plt> | |
0x000000000040053b <+14>: nop | |
0x000000000040053c <+15>: pop %rbp | |
0x000000000040053d <+16>: retq | |
End of assembler dump. |
由此可知,其實在function在記憶體中也是有特定儲存的位址。
如果我們在linux的環境中使用 objdump 這個指令可以看得更清楚,他會指名在.text區
Disassembly of section .text:
.....
.....
000000000040052d <say_hello>:
------------------------ 2. 如何宣告一個函式指標 ------------------------
宣告方式: return_type (*func_pointer)( parameter list );
例如:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<stdio.h> | |
int add(int n1, int n2){ | |
return n1 + n2; | |
} | |
int main(){ | |
int (*fptr)(int, int); | |
fptr = add; | |
int num1 = 10, num2 = 30; | |
printf("num1 + num2 is : %d\n", fptr(num1, num2)); | |
return 0; | |
} |
第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 也可以執行:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<stdio.h> | |
void print_hello(){ | |
printf("Hello world\n"); | |
return; | |
} | |
int main(){ | |
print_hello(); | |
(&*print_hello)(); | |
(*&*print_hello)(); | |
(**&print_hello)(); | |
(&**&print_hello)(); | |
(&*&*print_hello)(); | |
return 0; | |
} |
要確保初值化(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 大神