函數名,到底是什么?


    函數名,到底是什么?這個問題是我看了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;

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM