[X]关闭

函数名到底是什么?

文档创建者:宋桓公
浏览次数:7423
最后更新:2020-02-27
悬赏1积分未解决
    函数名,到底是什么?这个问题是我看了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;
}


发表评论已发布 2

星空下的平凡

发表于 2020-2-25 11:20:02 | 显示全部楼层

就是一个标识
回复

使用道具 举报

ぉ沙皮狗的忧伤

发表于 2020-2-27 10:00:24 | 显示全部楼层

函数指针数组,首先它是一个数组里面存放着函数指针,函数指针是一个指针指向函数,函数名则是函数的入口地址
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则