在之前的一篇文章【C語言】沒想到指針還能這么用 @?。。≈薪榻B了【函數(shù)指針】的基本概念和簡(jiǎn)單應(yīng)用;今天再給大家分享一個(gè)【函數(shù)指針】的高級(jí)應(yīng)用;在嵌入式系統(tǒng)開發(fā)中,此類用法非常地常見,但如果對(duì)【函數(shù)指針】的理解不夠透徹,很有可能會(huì)看得一頭霧水。
代碼片段如下:
typedef void (*kernel_func)(void);
void jump_to_kernel(void)
{
uint32_t *kernel_start = (uint32_t *)0x410000;
kernel_func func = (kernel_func)kernel_start[0];
printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, func);
local_irq_disable();
func();
}
我們來分析下這段代碼:
從函數(shù)名,我們可以知道,這個(gè)函數(shù)的功能就是實(shí)現(xiàn)從boot程序到kernel程序的跳轉(zhuǎn),即boot程序?qū)ernel程序跑起來。
函數(shù)的第一句代碼:uint32_t *kernel_start = (uint32_t *)0x410000; 告訴我們kernel程序的執(zhí)行地址是0x410000;這是一個(gè)絕對(duì)地址,在C語言中,地址就是可運(yùn)行代碼的起始位置,它一般就是一個(gè)整型數(shù),比如在32位的CPU上,它就是一個(gè)32位的整型數(shù)。
函數(shù)的第二句代碼:kernel_func func = (kernel_func)kernel_start[0]; 這里用到了一個(gè)typedef定義的函數(shù)指針別名,它的定義為:typedef void (*kernel_func)(void); 它定義了函數(shù)指針,此指針指向一種函數(shù),這種函數(shù)返回值為void型,入?yún)⒁矠関oid。所以 kernel_func func = (kernel_func)kernel_start[0]; 這整一句代碼的意思就是: 定義一個(gè)名稱為func的函數(shù)指針,它指向一個(gè)由kernel_start這個(gè)變量(地址為0x410000)代表的函數(shù),為了保證函數(shù)指針賦值的正確性,還加上了(kernel_func)做強(qiáng)制類型轉(zhuǎn)換。
函數(shù)的第三局代碼為printk打印輸出,不在此討論范圍內(nèi)。
函數(shù)的第四句代碼:local_irq_disable(); 表示關(guān)閉當(dāng)前系統(tǒng)的中斷,為kernel程序的運(yùn)行創(chuàng)造干凈的環(huán)境,也不在此討論的范圍。
函數(shù)的第五句代碼:func(); 非常的干凈、簡(jiǎn)單。就一個(gè)簡(jiǎn)單的func執(zhí)行就完成了從boot程序切換到kernel程序運(yùn)行。這就是【函數(shù)指針】的魅力所在,在執(zhí)行func()之前,它已經(jīng)指向了kernel程序的起始地址0x41000,根據(jù)【函數(shù)指針】的語法規(guī)則,執(zhí)行func(),實(shí)則就是執(zhí)行0x410000這個(gè)地址對(duì)應(yīng)的函數(shù),也就把kernel程序跑起來了。
以上分析,相信有一定C語言基礎(chǔ)的童鞋,都能分析得出來。但是,當(dāng)我接觸到這段代碼的時(shí)候,看了下那句printk調(diào)試輸出,我發(fā)現(xiàn)了一個(gè)疑問: %08x輸出func時(shí),居然輸出的不是0x410000,而是一個(gè)可能跟0x410000毫不相干的數(shù)值。
why ? 到底是為什么??? 當(dāng)時(shí)我好納悶,函數(shù)的第一句不是把kernel_start變量賦值為0x410000,然后函數(shù)的第二句不是把func這個(gè)函數(shù)指針變量賦值為kernel_start,這不就是相當(dāng)于func就等于0x41000嗎?這也有錯(cuò)?
為了一探究竟,我特意請(qǐng)教了一個(gè)別的部門同事,當(dāng)時(shí)他幫我捋了捋,兩人最后得出的一致結(jié)論就是: 既然是函數(shù)指針,終究是個(gè)指針,那么按照我們的需求,應(yīng)該理解為 “指針的內(nèi)容是0x41000”,而根據(jù)指針的訪問規(guī)則,訪問其內(nèi)容應(yīng)該要加*符號(hào),所以*func的輸出才是0x410000,func的輸出是其他值。當(dāng)時(shí)這個(gè)說法,我也是認(rèn)同的;不過抱著嚴(yán)謹(jǐn)好學(xué)的態(tài)度,我還是決定在代碼是調(diào)試調(diào)試,一試便知真假。
但是,不試不知道,一試嚇一跳。我將printk那句代碼,改了下:
?編輯
輸出的結(jié)果:竟然是只有kernel_start輸出的是0x410000,其他的幾個(gè)輸出都是同樣的一個(gè)別的數(shù)值。
?編輯
kernel程序的bin文件的hexdump圖片: 【小端模式存儲(chǔ)】
為什么啊?
我們重新捋一捋這段代碼:
先明確各個(gè)變量是什么東西:
func是一個(gè)函數(shù)指針變量
kernel_start是一個(gè)指針變量,即它是一個(gè)地址,說白了就是一個(gè)數(shù)值
kernel_start[0] 可以這么理解,kernel_start是一個(gè)數(shù)組名,即數(shù)組的首地址,取它的第一個(gè)元素
再分析下每句打印語句:
printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, kernel_start); 輸出00410000最好理解,它就是打印一個(gè)數(shù)值,肯定就是00410000
printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, *kernel_start); 輸出00419690,需要轉(zhuǎn)換下思路,kernel_start是一個(gè)指針;
所以*kernel_start打印的是指針指向的內(nèi)容,打印不是00410000,而是00410000地址存放的內(nèi)容,即00419690
printk("%s()%d: %08x\n", __FUNCTION__, __LINE__, kernel_start[0]); 把kernel_start理解成一個(gè)數(shù)組名(指針和數(shù)組名有相通之處)
我們也不難推斷出kernel_start[0]打印的應(yīng)該是00419690,而不是00410000
最后分析有關(guān)func的輸出,為何都是00419690,而不是00410000:
函數(shù)指針的特殊性,與普通指針不太一樣,如一個(gè)函數(shù)指針p指向了一個(gè)已經(jīng)定義函數(shù)test_func,那么相當(dāng)于 *p 等同于 test_func (與指針的基本概念一致)所以調(diào)用函數(shù)時(shí),可以使用 test_func(param_in),也可以使用(*p)(param_in);兩者是等價(jià)的。
根據(jù)指針與數(shù)組名的關(guān)系類比,調(diào)用函數(shù)也可以使用 p(param_in) 和 (*test_func)(param_in)。
根據(jù)上面的分析,我們可以得出結(jié)論,當(dāng)是用%08x打印func和*func的時(shí)候,打印的都是地址00410000指向的內(nèi)容00419690,而不是地址值00410000
以下圖片是從C語言的教科書截取的;
函數(shù)指針,不知你繞暈了沒?
延伸閱讀:
【C語言】沒想到指針還能這么用 @!??!
?審核編輯:湯梓紅
-
C語言
+關(guān)注
關(guān)注
180文章
7595瀏覽量
135932 -
Boot
+關(guān)注
關(guān)注
0文章
149瀏覽量
35753 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1265瀏覽量
39855 -
Kernel
+關(guān)注
關(guān)注
0文章
48瀏覽量
11124
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論