函数名,到底是什么?

    函数名,到底是什么?这个问题是我看了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;

}

 

posted @ 2016-06-23 17:16  宋桓公  阅读(8351)  评论(0编辑  收藏  举报