zoukankan      html  css  js  c++  java
  • 函数名,到底是什么?

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

    }

  • 相关阅读:
    jsp 特殊标签
    poj 1753 Flip Game 高斯消元 异或方程组 求最值
    zoj 3155 Street Lamp 高斯消元 异或方程组 求方案数
    poj1222 EXTENDED LIGHTS OUT 高斯消元解异或方程组 模板
    zoj 3930 Dice Notation 模拟
    zoj 3157 Weapon 线段树求逆序对数
    hdu 1242 Rescue BFS+优先队列
    hdu 3466 Proud Merchants 贪心+01背包
    zoj 3689 Digging 贪心+01背包
    hdu 2602 Bone Collector 01背包模板
  • 原文地址:https://www.cnblogs.com/douzi2/p/5611491.html
Copyright © 2011-2022 走看看