函數名,到底是什么?這個問題是我看了uboot里的一個“函數指針數組”的應用而問自己的。
如果不把函數名理解為函數指針,就無法理解“函數指針數組”的訪問方式。
首先看看指針的概念:
指針變量就具有3種形態:
1、a 表示指針a對應的內存空間(可以說就是指針本身的值,或者說是指針指向的地址值,這個值存在於a對應的內存空間)
2、&a 表示當前指針對應的內存空間的首地址(存放指針的地址)
3、*a 表示指針a所指向的變量對應的整個內存空間。(指針指向的空間)
一般情況下,這三者的值明顯是不相同的。但是對於函數名來說,這三者的值居然是一樣的!
int fnc1(void)
{
printf("1\n");
return 0;
}
fnc1 是一個函數名,下面打印出值完全一樣:
printf("fnc1 = %x\n",(unsigned int)fnc1);
printf("fnc1 = %x\n",(unsigned int)(*fnc1));
printf("fnc1 = %x\n",(unsigned int)(&fnc1));
並且,調用函數效果也完全一樣:
(*fnc1)();
(&fnc1)();
fnc1();
甚至可以寫成:
(*******fnc1)(); //調用也是一樣
不能寫成(&&&&fnc1)(); 這樣只是因為&&表示一個新的符號‘邏輯與’。
從上面的結果看來,函數名並不是一個指針,而是一個相當奇葩的玩意。。
但是,我認為正確的理解方式應該是:我們必須把函數名,當成指針看待。至於我們
最常見的函數調用方式:fnc1(); 只是(*fnc1)();簡寫形式而已。我們之所以可以fnc1();這樣調用函數,只是編譯器幫我們做了調整而已。
這樣理解是有好處的,比如理解下“函數指針數組”
#include <stdio.h>
int fnc1(void)
{
printf("1\n");
return 0;
}
int fnc2(void)
{
printf("2\n");
return 0;
}
int fnc3(void)
{
printf("3\n");
return 0;
}
typedef int (init_fnc_t) (void);
init_fnc_t** init_fnc_ptr;
init_fnc_t* init_fnc_sequence[] = {
fnc1,
fnc2,
fnc3,
NULL
};
int main(int argc, char *argv[])
{
for (init_fnc_ptr = init_fnc_sequence; *init_fnc_ptr; ++init_fnc_ptr)
{
(*init_fnc_ptr)();
printf("*init_fnc_ptr = %x\n",(unsigned int)(*init_fnc_ptr));
printf("init_fnc_ptr = %x\n",(unsigned int)(&(*init_fnc_ptr)));
}
printf("the end\n");
printf("fnc1 = %x\n",(unsigned int)fnc1);
printf("fnc1 = %x\n",(unsigned int)(*fnc1));
printf("fnc1 = %x\n",(unsigned int)(&fnc1));
return 0;
}
這段程序的原型來自於uboot,我將其簡化。首先定義了3個函數fnc1,fnc3,fnc3
然后定義一個函數類型 typedef int (init_fnc_t) (void);
再定義一個二重函數指針 init_fnc_t** init_fnc_ptr;
最后定義一個函數指針數組 init_fnc_t* init_fnc_sequence[]
函數指針數組,其實就是指針數組,只是說這個數組里放的是指針變量,而指針變量類型是函數指針類型。但是我們發現這個數組里放的其實就是函數名。這里其實就說明了函數名就是指針類型。
順帶說下指針數組:
如int* a[],這就是個指針數組,或者說是整形指針數組。區別於數組指針:(int* a)[]
再來看,二重指針的問題:
我們知道一重指針,可以訪問一維普通數組;
那二重指針,其實就是可以訪問一維指針數組;本質上就是二重指針指向一重指針的地址。
知道了以上這些結論,再來看這主程序,才能體會到函數名的真正含義。
我們定位到main函數中的這for循環。
一開始,init_fnc_ptr = init_fnc_sequence
將init_fnc_sequence數組名,賦值給init_fnc_ptr二重指針。
數組名意味着將首元素的首地址,那么就是說將fnc1(函數名)的地址賦值給init_fnc_ptr二重指針。這里再次說明你必須將fnc1(函數名)認為是一個指針。因為只有指針的地址
才是和二重指針配對啊。
第二句 *init_fnc_ptr 就二重指針一次解引用,得到數組中的值,也就是函數名本身,
或者說是函數指針本身。(如果這個值是等於NULL就會跳出循環,也就是數組里最后一個是NULL的原因。)
也就是說此時如果將*init_fnc_ptr看成一個整體的話,他就相當於一個函數名。
從打印的結果來看*init_fnc_ptr值也分別依次等於函數名的值。
*init_fnc_ptr = 401214 fnc1 = 401214
但是如果對*init_fnc_ptr取址,得到什么呢?是不是和函數名一樣,值不變呢?
答案就否定的:
*init_fnc_ptr = 401214 (&(*init_fnc_ptr)= 402008
(&(*init_fnc_ptr)的值不等於*init_fnc_ptr的值。其實(&(*init_fnc_ptr)的值和init_fnc_ptr
相同,是數組元素的地址值,也只有這樣++init_fnc_ptr才有意義。只有這樣我們
才能通過++init_fnc_ptr來進行偏移訪問不同的函數。
寫到這里可以小節一番了:
1、函數指針值與函數地址值相等。
2、對於函數名func來說,不管是*func還是func還是&func,編譯器都認為他是函數指針,一般情況下你無法得知函數指針的地址。
而對於這個函數指針的地址(或者說是函數指針存放的位置我們無法知道),也只有
借助函數指針數組,你才能知道函數指針的地址。
//--------------------------------------------------------------------------------------------------------
繼續來討論一個uboot的中的一個例子:
我們知道210的irom中固化了一個函數(用來將SD/MMC的值拷貝到DDR),他只提供了一個地址(0xD0037F98),和函數的聲明。
用法:
typedef bool(*pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int*, bool);
// 第一步,讀取SD卡扇區到DDR中
pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)(*(unsigned int *)0xD0037F98); //測試這樣可以
//pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)0xD0037F98); //程序卡死
從而看出,0xD0037F98並不是函數的地址(或者說是函數指針的值),而應該是函數指針的地址值。0xD0037F98這個地址對應的空間中的值才是函數地址的值。
然后是,
typedef void (*pBL2Type)(void);
pBL2Type p2 = (void *)0x23E00000;p2();
這句話的作用是利用函數的特性,做一個長跳轉(絕對地址跳轉)
讓人不明白的是為什么是用 (void *)強制轉換類型0x23E00000?
其實你也可以:pBL2Type p2 = (pBL2Type)0x23E00000;
甚至可以:pBL2Type p2 = 0x23E00000; 只是此時有個轉換上的警告。
可以寫成 (void *)也沒有警告的原因是, (void *)是指針,pBL2Type也是指針(函數指針)。
但是(void *)可以轉換為任何類型的指針,所以這里不會警告。
下面是為了證明以上所有結論的測試代碼,思路是先打印出一個函數的地址,
再利用這個已知的地址,做一些測試:
#include <stdio.h>
int fnc(char a)
{
printf("%d\n",a);
return 1;
}
typedef int (*ff)(char a);
ff fnc1 = (ff)((unsigned int*)(0x401214));
ff fnc2 = (void *)0x401214;
int main(int argc, char *argv[])
{
int r = 0;
int (*BL2)(char a);
printf("fnc = %x\n",(unsigned int)fnc);
BL2 = (void *)0x401214;//BL2 = (void(*)(void))0x401214;
printf("------(*BL2)()-----\n");
r = (*BL2)(1);
printf("r = %d\n",r);
printf("-------------------\n");
printf("--------BL2()------\n");
r = BL2(2);
printf("r = %d\n",r);
printf("-------------------\n");
printf("-------fnc1()-------\n");
r = fnc1(3);
printf("r = %d\n",r);
printf("-------------------\n");
printf("-------fnc2()-------\n");
r = fnc2(4);
printf("r = %d\n",r);
printf("-------------------\n");
return 0;
}
