深入淺出剖析C語(yǔ)言函數(shù)指針與回調(diào)函數(shù)
微信公眾號(hào):楊源鑫
如果你覺(jué)得本文對(duì)你有幫助,歡迎留言探討!
一、C語(yǔ)言回調(diào)函數(shù)
什么是回調(diào)函數(shù)?
百度的權(quán)威解釋如下:
回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)。
1#include
2void print();
3int main(void)
4{
5 void (*fuc)();
6 fuc = print ;
7 fuc();
8}
9void print()
10{
11 printf("hello world!\n");
12}
運(yùn)行結(jié)果:
從這個(gè)例子可以看到,我們首先定義了一個(gè)函數(shù)指針fuc ,這個(gè)函數(shù)指針的返回值為void型,然后我們給函數(shù)指針賦值,賦值為print,也就是print函數(shù)的首地址,此時(shí)fuc獲得了print的地址,fuc的地址等于print的地址,所以最終調(diào)用fuc();也就相當(dāng)于調(diào)用了print();那么我寫(xiě)的這個(gè)例子明顯和百度解釋的不符合?。慷x是如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù),確實(shí),有所不同,但道理是一樣的,我們接下來(lái)再來(lái)看一個(gè)例子。
1#include
2
3int add_ret() ;
4
5int add(int a , int b , int (*add_value)())
6{
7 return (*add_value)(a,b);
8}
9
10int main(void)
11{
12 int sum = add(3,4,add_ret);
13 printf("sum:%d\n",sum);
14 return 0 ;
15}
16
17int add_ret(int a , int b)
18{
19 return a+b ;
20}
運(yùn)行結(jié)果:
從這個(gè)例子里,我們看到:
這樣子不就符合我們的定義了嘛?我們把函數(shù)的指針(地址),這里也就是add_ret,作為參數(shù)int add(int a , int b , int (add_value)()) , 這里的參數(shù)就是int(add_value)() , 這個(gè)名字可以隨便取,但是要符合C語(yǔ)言的命名規(guī)范。當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。 我們看到add函數(shù)內(nèi)部,return (add_value)(a,b) ; 這個(gè)(add_value)(a,b)相當(dāng)于對(duì)指針進(jìn)行了簡(jiǎn)引用,我們?cè)趍ain函數(shù)中,傳入具體要實(shí)現(xiàn)功能的函數(shù),add_ret,這個(gè)函數(shù)很簡(jiǎn)單,就是實(shí)現(xiàn)兩數(shù)相加并返回,這里剛剛好,簡(jiǎn)引用,相當(dāng)于取出指針?lè)祷氐刂防锏闹?,這個(gè)值就是return a+b,也就是我們傳入a和b兩數(shù)相加的結(jié)果。
那么,回調(diào)函數(shù)究竟有什么作用呢?
說(shuō)到這里,就有了用戶和開(kāi)發(fā)者之間的概念,假設(shè),用戶是實(shí)現(xiàn)add_ret這個(gè)函數(shù),而開(kāi)發(fā)者是實(shí)現(xiàn)add這個(gè)函數(shù),現(xiàn)在的需求是,用戶將add_ret這個(gè)函數(shù)以參數(shù)的形式傳入開(kāi)發(fā)者實(shí)現(xiàn)的add函數(shù),add函數(shù)就會(huì)返回一個(gè)數(shù)字給用戶,開(kāi)發(fā)者沒(méi)必要告訴用戶他實(shí)現(xiàn)了什么東西,用戶也并不知道開(kāi)發(fā)者是怎么實(shí)現(xiàn)的,用戶只需要傳入自己寫(xiě)的函數(shù),便可以得到開(kāi)發(fā)者實(shí)現(xiàn)的函數(shù)的返回值,開(kāi)發(fā)者可以將內(nèi)容封裝起來(lái),將頭文件以及庫(kù)文件提供給用戶。
接下來(lái),我們用Linux來(lái)演示下這個(gè)結(jié)果,我們?cè)谀夸浵聞?chuàng)建三個(gè)文件main.c,vendor.c,vendor.h
main.c是用戶開(kāi)發(fā)的。
vendor.c和vendor.h是開(kāi)發(fā)者實(shí)現(xiàn)的。
在main.c中,代碼如下:
1#include
2#include "vendor.h"
3
4int add_ret(int a , int b)
5{
6 return a + b ;
7}
8
9int main(void)
10{
11 int sum = add(3,4,add_ret);
12 printf("sum:%d\n",sum);
13 return 0 ;
14}
vendor.c,代碼如下:
1#include "vendor.h"
2int add(int a , int b , int (*add_value)())
3{
4 return (*add_value)(a,b);
5}
vendor.h,代碼如下:
1#ifndef __VENDOR_H
2#define __VENDOR_H
3
4int add(int a , int b , int (*add_value)());
5
6#endif
接下來(lái),我們制作一個(gè)動(dòng)態(tài)鏈接庫(kù),最終開(kāi)發(fā)者把vendor.c的內(nèi)容封起來(lái),把vendor.h提供給用戶使用。
1#include
2#include "vendor.h"
3
4int add_ret(int a , int b)
5{
6 return a + b ;
7}
8
9int main(void)
10{
11 int sum = add(3,4,add_ret);
12 printf("sum:%d\n",sum);
13 return 0 ;
14}
在linux下制作動(dòng)態(tài)鏈接庫(kù),將vendor.c和vendor.h打包成一個(gè)動(dòng)態(tài)鏈接庫(kù)
先明白以下幾個(gè)命令是什么意思:
生成動(dòng)態(tài)庫(kù):
gcc -shared -fPIC dvendor.c -o libvendor.so
參數(shù)含義:
-shared : 生成動(dòng)態(tài)庫(kù);
-fPIC : 生成與位置無(wú)關(guān)代碼;
-o :指定生成的目標(biāo)文件;
使用動(dòng)態(tài)庫(kù):
gcc main.c -L . –lvendor -o main
-L : 指定庫(kù)的路徑(編譯時(shí)); 不指定就使用默認(rèn)路徑(/usr/lib/lib)
-lvendor : 指定需要?jiǎng)討B(tài)鏈接的庫(kù)是誰(shuí);
代碼運(yùn)行時(shí)需要加載動(dòng)態(tài)庫(kù):
./main 加載動(dòng)態(tài)庫(kù) (默認(rèn)加載路徑:/usr/lib /lib ./ …)
./main
我們將編譯動(dòng)態(tài)庫(kù)生成的libvendor.so拷貝到/usr/lib后,現(xiàn)在就不需要vendor.c了,此時(shí)我們將vendor.c移除,也可以正常的編譯并且執(zhí)行main函數(shù)的結(jié)果,這就是回調(diào)函數(shù)的作用之一。
操作流程如下:
二、回調(diào)函數(shù)在Linux內(nèi)核中的應(yīng)用
回調(diào)函數(shù)在Linux內(nèi)核里得到了廣泛的應(yīng)用,接下來(lái),我將引用Linux內(nèi)核中文件操作結(jié)構(gòu)體來(lái)詳細(xì)的說(shuō)明。
我們首先來(lái)看到這個(gè)結(jié)構(gòu)體,這段代碼位于linux內(nèi)核的include/linux/fs.h中,由于代碼眾多,我只截取幾個(gè)最基本的例子:
File_operations文件操作結(jié)構(gòu)體:
1struct file_operations {
2 struct module *owner;
3 loff_t (*llseek) (struct file *, loff_t, int);
4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
7 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
8 int (*readdir) (struct file *, void *, filldir_t);
9 unsigned int (*poll) (struct file *, struct poll_table_struct *);
10 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
11 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
12 int (*mmap) (struct file *, struct vm_area_struct *);
13 int (*open) (struct inode *, struct file *);
14 int (*flush) (struct file *, fl_owner_t id);
15 int (*release) (struct inode *, struct file *);
16 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
17 int (*aio_fsync) (struct kiocb *, int datasync);
18 int (*fasync) (int, struct file *, int);
19 int (*lock) (struct file *, int, struct file_lock *);
20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
22 int (*check_flags)(int);
23 int (*flock) (struct file *, int, struct file_lock *);
24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
26 int (*setlease)(struct file *, long, struct file_lock **);
27 long (*fallocate)(struct file *file, int mode, loff_t offset,
28 loff_t len);
29};
這段代碼中,利用結(jié)構(gòu)體的封裝思想,將函數(shù)指針?lè)庋b在一個(gè)file_operations結(jié)構(gòu)體里,然后,在具體實(shí)現(xiàn)驅(qū)動(dòng)的時(shí)候,實(shí)現(xiàn)具體的函數(shù),再賦值給結(jié)構(gòu)體里的函數(shù)指針做好初始化操作,我們來(lái)看看友善之臂的led驅(qū)動(dòng)就明白了。
以下這段代碼截取友善之臂提供的linux內(nèi)核中的tiny4412_led.c
1static struct file_operations tiny4412_led_dev_fops = {
2 .owner = THIS_MODULE,
3 .unlocked_ioctl = tiny4412_leds_ioctl,
4};
5
6static struct miscdevice tiny4412_led_dev = {
7 .minor = MISC_DYNAMIC_MINOR,
8 .name = DEVICE_NAME,
9 .fops = &tiny4412_led_dev_fops,
10};
首先,先是定義了一個(gè)結(jié)構(gòu)體變量,并對(duì)結(jié)構(gòu)體變量進(jìn)行初始化,在這個(gè)驅(qū)動(dòng)中,只實(shí)現(xiàn)了ioctl函數(shù),對(duì)照著上面的結(jié)構(gòu)體,ulocked_ioctl就是結(jié)構(gòu)體中的這個(gè)函數(shù)指針。
long (*unlocked_ioctl) (struct file *,unsigned int, unsigned long);
再來(lái)看看友善實(shí)現(xiàn)的adc驅(qū)動(dòng)里,也是這么來(lái)做,這里看到 : 也是C語(yǔ)言結(jié)構(gòu)體的一種初始化方式,也是合理的。
1static struct file_operations adc_dev_fops = {
2 owner: THIS_MODULE,
3 open: exynos_adc_open,
4 read: exynos_adc_read,
5 unlocked_ioctl: exynos_adc_ioctl,
6 release: exynos_adc_release,
7};
8static struct miscdevice misc = {
9 .minor = MISC_DYNAMIC_MINOR,
10 .name = "adc",
11 .fops = &adc_dev_fops,
12};
在內(nèi)核中,有很多這樣的函數(shù)指針,所以,當(dāng)我們了解了這樣的套路以后,再去學(xué)習(xí)linux內(nèi)核,我們的思想就會(huì)清晰很多了。
再來(lái)看看回調(diào)函數(shù)在linux內(nèi)核里的基本應(yīng)用。
從上節(jié)我們了解到,回調(diào)函數(shù)的本質(zhì)其實(shí)也就是函數(shù)指針,只不過(guò)定義有所區(qū)別。它的定義就是:你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用來(lái)調(diào)用其所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。
接下來(lái)我們來(lái)看一個(gè)例子:
這段代碼摘自友善之臂的button驅(qū)動(dòng):
1static int tiny4412_buttons_open(struct inode *inode, struct file *file)
2{
3 int irq;
4 int i;
5 int err = 0;
6
7 for (i = 0; i < ARRAY_SIZE(buttons); i++) {
8 if (!buttons[i].gpio)
9 continue;
10
11 setup_timer(&buttons[i].timer, tiny4412_buttons_timer,
12 (unsigned long)&buttons[i]);
13
14 irq = gpio_to_irq(buttons[i].gpio);
15 err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
16 buttons[i].name, (void *)&buttons[i]);
17 if (err)
18 break;
19 }
20
21 if (err) {
22 i--;
23 for (; i >= 0; i--) {
24 if (!buttons[i].gpio)
25 continue;
26
27 irq = gpio_to_irq(buttons[i].gpio);
28 disable_irq(irq);
29 free_irq(irq, (void *)&buttons[i]);
30
31 del_timer_sync(&buttons[i].timer);
32 }
33
34 return -EBUSY;
35 }
36
37 ev_press = 1;
38 return 0;
39}
我們?cè)趖iny4412_buttons_open函數(shù)里看到
1err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
2 buttons[i].name,(void *)&buttons[i]);
我們來(lái)看看request_irq這個(gè)函數(shù):
1static inline int __must_check
2request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
3 const char *name, void *dev)
4{
5 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
6}
到這里我們就明白了,第二個(gè)參數(shù)是一個(gè)用typedef重新定義的一個(gè)新類型的函數(shù)指針。
那么也就是說(shuō)一旦執(zhí)行了tiny4412的open函數(shù),就會(huì)通過(guò)request_irq去通過(guò)回調(diào)函數(shù)去執(zhí)行按鍵中斷,并返回一個(gè)中斷句柄。這個(gè)回調(diào)函數(shù),其實(shí)就是一個(gè)中斷服務(wù)函數(shù)。
1static irqreturn_t button_interrupt(int irq, void *dev_id)
2{
3 struct button_desc *bdata = (struct button_desc *)dev_id;
4
5 mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(40));
6
7 return IRQ_HANDLED;
8}
回調(diào)函數(shù)在內(nèi)核中就是這么來(lái)使用的,當(dāng)然,還有其它的,比如我們?cè)趖iny4412的open函數(shù)里面還看到:
1 setup_timer(&buttons[i].timer,tiny4412_buttons_timer,
2 (unsignedlong)&buttons[i]);
這個(gè)函數(shù)的作用是注冊(cè)一個(gè)定時(shí)器,通過(guò)回調(diào)函數(shù)tiny4412_buttons_timer來(lái)進(jìn)行觸發(fā)。
如果你不看它的定義,你可能以為它是一個(gè)普通函數(shù),其實(shí)它是一個(gè)宏封裝的。
1#define setup_timer(timer, fn, data) \
2 do { \
3 static struct lock_class_key __key; \
4 setup_timer_key((timer), #timer, &__key, (fn), (data));\
5 } while (0)
這個(gè)宏函數(shù)通過(guò)調(diào)用setup_timer_key這個(gè)函數(shù)來(lái)實(shí)現(xiàn)定時(shí)器的注冊(cè):
1static inline void setup_timer_key(struct timer_list * timer,
2 const char *name,
3 struct lock_class_key *key,
4 void (*function)(unsigned long),
5 unsigned long data)
6{
7 timer->function = function;
8 timer->data = data;
9 init_timer_key(timer, name, key);
10}
通過(guò)這個(gè)例子,我們更加了解到回調(diào)函數(shù)在Linux內(nèi)核中的應(yīng)用,為學(xué)習(xí)Linux內(nèi)核,分析linux內(nèi)核源代碼打下了基礎(chǔ)。
三、回調(diào)函數(shù)在Posix應(yīng)用API中的使用
其實(shí),在Posix應(yīng)用編程里,我們也能用到回調(diào)函數(shù),我們來(lái)看看多線程編程中,經(jīng)常使用的pthread_create函數(shù):
我們先來(lái)看看它的原型:
1int pthread_create((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
參數(shù)說(shuō)明:
(1) thread:表示線程的標(biāo)識(shí)符
(2) attr:表示線程的屬性設(shè)置
(3) start_routine:表示線程函數(shù)的起始地址
(4) arg:表示傳遞給線程函數(shù)的參數(shù)
函數(shù)的返回值為:
(1) success:返回0
(2) fair:返回-1
看到這個(gè)函數(shù)的第三個(gè)參數(shù),這不就是一個(gè)函數(shù)指針,同時(shí)也是一個(gè)回調(diào)函數(shù)嘛!這就是函數(shù)指針和回調(diào)函數(shù)在UNIX環(huán)境多線程編程中的應(yīng)用。
我們?cè)趙indows的dev C++上寫(xiě)一個(gè)測(cè)試程序來(lái)看看:
1#include
2#include
3
4
5void *function(void *args)
6{
7 while(1)
8 {
9 printf("hello world1!\n");
10 sleep(1);
11 }
12}
13int main(void)
14{
15 pthread_t tid ;
16 tid = pthread_create(&tid , NULL , function , NULL);
17 while(1)
18 {
19 printf("hello world!\n");
20 sleep(1);
21 }
22 return 0 ;
23}
運(yùn)行結(jié)果:
我們會(huì)看到在main函數(shù)里的打印語(yǔ)句和在線程回調(diào)函數(shù)void *function(void *args)里打印語(yǔ)句在同時(shí)打印。
關(guān)于這個(gè)函數(shù)的如何使用,網(wǎng)上文章有很多講得非常的詳細(xì),這里僅僅只是寫(xiě)函數(shù)指針和回調(diào)函數(shù)的應(yīng)用,詳細(xì)可以參考這篇文章,了解進(jìn)程和線程。
http://blog.csdn.net/tommy_wxie/article/details/8545253
當(dāng)然,應(yīng)用里還有其它的API通用運(yùn)用到了回調(diào)函數(shù),期待大家在實(shí)踐中去發(fā)掘。
另外,推薦一下韋東山老師的嵌入式課程:
移動(dòng)互聯(lián)網(wǎng),人工智能,物聯(lián)網(wǎng)等技術(shù)正在飛速發(fā)展,這都離不開(kāi)嵌入式技術(shù)的支持,如果您想創(chuàng)業(yè)自己開(kāi)發(fā)產(chǎn)品,韋東山老師的視頻一定可以幫助您完成創(chuàng)客夢(mèng)想。
[分享]韋東山嵌入式linux 第1期 ARM裸機(jī)實(shí)戰(zhàn)
https://j.youzan.com/zRcvW9
[分享]韋東山嵌入式linux第2期 驅(qū)動(dòng)大全
https://j.youzan.com/o3nvW9
[分享]韋東山嵌入式linux 第3期 項(xiàng)目實(shí)戰(zhàn)
https://j.youzan.com/z0cvW9
[分享]韋東山第4期 Android系統(tǒng)視頻
https://j.youzan.com/ifMlW9
[分享]韋東山嵌入式Linux arm 不帶s3c2440開(kāi)發(fā)板java入門(mén)視頻
https://j.youzan.com/1VnvW9
[分享]韋東山嵌入式linux視頻不帶ARM9開(kāi)發(fā)板c++入門(mén) 手把手指導(dǎo)
https://j.youzan.com/NdnvW9
[分享]韋東山嵌入式Linux SPI arm視頻不帶s3c2440開(kāi)發(fā)板 手把手指導(dǎo)
https://j.youzan.com/xHcvW9
[分享]錄制完畢韋東山嵌入式Linux視頻JZ2440s3c2440開(kāi)發(fā)板設(shè)備樹(shù)詳解
https://j.youzan.com/lmnvW9
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!