• 函数名,到底是什么?


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

    }

  • 相关阅读:
    cmd设置代理
    移动端坐标定位tap
    T02-Django基本应用结构
    支持向量机算法的Sklearn完整复现
    T01-何为Django,入门"Hello World"
    决策树算法的Sklearn完整复现
    逻辑回归算法的Sklearn完整复现
    线性回归算法Sklearn完整复现
    K-近邻算法的Sklearn完整复现
    数据分析中的'疑难杂症'小结(三)
  • 原文地址:https://www.cnblogs.com/douzi2/p/5611491.html
Copyright © 2020-2023  润新知