函數指針與回調函數詳解
1.什么是函數指針?
函數(的)指針就是指針。這個指針存放一個函數的地址,而函數的名稱就該函數的入口,即地址。這類似于數組名就是數組的首地址。我們可以通過反匯編直觀的查看到函數名和函數地址的關系。
080483c4 <rfun>:80483c4: 55 push %ebp80483c5: 89 e5 mov %esp,%ebp80483c7: 83 ec 28 sub $0x28,%esp······80483f3: e8 cc ff ff ff call 80483c4 <rfun>······
第一行080483c4就是函數在內存中的地址,后面的 rfun 就是函數名,還可以看出第二行和第一行的地址相同,所以可以知道函數名就是該函數的入口地址。?
第6行的call 80483c4 rfun就是再次調用這個函數,回到這個函數的入口出執行,可以看出該函數是一個遞歸函數。
2.函數指針的使用
2.1調用函數
我們就用函數指針調用剛才的rfun函數。
int rfun(unsigned x);//函數的聲明
通過函數的聲明,可以得到rfun函數的返回值類型,參數類型和參數個數。這些都是定義指針函數的必要條件!既然函數名就是函數地址,我們就可以用一個指針指向它,函數指針如下:
int (*pfun)(unsigned);//函數指針的定義
pfun = rfun;//函數指針的初始化
pfun就是指針變量,可以直接用函數名rfun賦值給pfun這個指針。函數指針在調用時和函數調用一致,只是用指針代替了函數名。
pfun(x);//函數指針的調用
函數指針不能做自增和自減操作,否則程序會崩潰。
2.2作為函數的參數
函數指針作為A函數的參數,A函數稱為回調函數。A函數的定義為:
int rfun_call(int (*pfun)(unsigned), unsigned x)
{return pfun(x);
}
這個函數的第一個參數是一個返回值為int類型,參數為一個且是unsigned類型函數的指針,第二個參數是 unsigned類型。
3.什么是回調函數?
回調函數(Callback Functions)就是一個通過函數指針調用的函數。
如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。剛才的rfun_call就是一個回調函數。這個函數是自己寫的,下面就用系統感受一下的回調函數。
先說明兩個Linux系統函數,一個是alarm函數,一個是signal函數
①alarm函數,也稱為鬧鐘函數,它可以在進程中設置一個定時器,當定時器指定的時間到時,它向進程發送SIGALRM信號。
#include <unistd.h>//alarm的頭文件
unsigned int alarm(unsigned int seconds);//alarm的函數原型
②signal函數,執行了signal()調用后,進程只要接收到類型為sig的信號,不管其正在執行程序的哪一部分,就立即執行func()函數。當func()函數執行結束后,控制權返回進程被中斷的那一點繼續執行。
#include <signal.h>//signal函數的頭文件typedef void (*sighandler_t)(int);//sighandler_t是一個指向返回值為void,參數為int的函數指針
sighandler_t signal(int signum, sighandler_t handler);//signal函數的原型
描述一個場景:?
你要睡覺,只睡2秒,所以你定了一個2秒的鬧鐘,到第3秒時,鬧鐘給你發信號,說時間到了
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void fun(int signum)
{printf("時間到了,起床!\n");
}int main()
{int i;alarm(3);//定2秒鬧鐘,第3秒會響鈴,也就是發信號signal(SIGALRM, fun);//接收鬧鈴的信號,去執行fun()for(i = 0; i < 3; i++){sleep(1);//描述時間的流逝printf("%d秒過去了\n", i+1);}printf("睡覺結束");return 0;
}
執行結果如下:
[root@menwen-linux test]# ./callback
1秒過去了
2秒過去了
時間到了,起床!
3秒過去了
睡覺結束
在時間到第三秒的時候,signal函數執行了fun函數,執行完fun函數,就會繼續執行“睡覺結束”。?
如果不設置鬧鈴(注釋alarm(3)),程序就會一直按順序執行,直到程序結束,永遠不會執行回調函數。?
signal函數就是一個回調函數,接收一個函數指針,和一個信號量SIGALRM,在Linux內核代碼中,SIGALRM等信號其實是一堆宏定義,都對應一個數值
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
......
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
等等.....
或者命令行輸入kill -l 也會對應出現這個信號量。
[menwen@menwen-linux 6th_day]$ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
......
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。我們實現了fun函數,而我們不調用該函數,而是去睡覺(或者干其他事),等到時間到,回調函數就會去響應,這是一種系統異步處理的機制。