函数名,到底是什么?这个问题是我看了uboot里的一个“函数指针数组”的应用而问自己的。
如果不把函数名理解为函数指针,就无法理解“函数指针数组”的访问方式。
首先看看指针的概念:
指针变量就具有3种形态:
1、a 表示指针a对应的内存空间(可以说就是指针本身的值,或者说是指针指向的地址值,这个值存在于a对应的内存空间)
2、&a 表示当前指针对应的内存空间的首地址(存放指针的地址)
3、*a 表示指针a所指向的变量对应的整个内存空间。(指针指向的空间)
一般情况下,这三者的值明显是不相同的。但是对于函数名来说,这三者的值居然是一样的!
int fnc1(void)
{
printf("1 ");
return 0;
}
fnc1 是一个函数名,下面打印出值完全一样:
printf("fnc1 = %x ",(unsigned int)fnc1);
printf("fnc1 = %x ",(unsigned int)(*fnc1));
printf("fnc1 = %x ",(unsigned int)(&fnc1));
并且,调用函数效果也完全一样:
(*fnc1)();
(&fnc1)();
fnc1();
甚至可以写成:
(*******fnc1)(); //调用也是一样
不能写成(&&&&fnc1)(); 这样只是因为&&表示一个新的符号‘逻辑与’。
从上面的结果看来,函数名并不是一个指针,而是一个相当奇葩的玩意。。
但是,我认为正确的理解方式应该是:我们必须把函数名,当成指针看待。至于我们
最常见的函数调用方式:fnc1(); 只是(*fnc1)();简写形式而已。我们之所以可以fnc1();这样调用函数,只是编译器帮我们做了调整而已。
这样理解是有好处的,比如理解下“函数指针数组”
#include <stdio.h>
int fnc1(void)
{
printf("1 ");
return 0;
}
int fnc2(void)
{
printf("2 ");
return 0;
}
int fnc3(void)
{
printf("3 ");
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 ",(unsigned int)(*init_fnc_ptr));
printf("init_fnc_ptr = %x ",(unsigned int)(&(*init_fnc_ptr)));
}
printf("the end ");
printf("fnc1 = %x ",(unsigned int)fnc1);
printf("fnc1 = %x ",(unsigned int)(*fnc1));
printf("fnc1 = %x ",(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 ",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 ",(unsigned int)fnc);
BL2 = (void *)0x401214;//BL2 = (void(*)(void))0x401214;
printf("------(*BL2)()----- ");
r = (*BL2)(1);
printf("r = %d ",r);
printf("------------------- ");
printf("--------BL2()------ ");
r = BL2(2);
printf("r = %d ",r);
printf("------------------- ");
printf("-------fnc1()------- ");
r = fnc1(3);
printf("r = %d ",r);
printf("------------------- ");
printf("-------fnc2()------- ");
r = fnc2(4);
printf("r = %d ",r);
printf("------------------- ");
return 0;
}