1、铺垫
在分析上面的语句前,我们先从简单的入手。先来区别和理解下面这两个定义。
float *g(); 和 float (*h) ();
直接上答案:由于( )结合的优先级别高于*,所以g先和后面的( )结合,构成一个函数,该函数的返回值是一个指向float 数的指针。同理,h是一个函数指针,它所指向的函数的返回值是一个浮点数。
当我们知道如何声明一个给定类型的变量,那么不难得出该类型的类型转换:只需要把声明中的变量名和声明末尾的分号去掉,然后将剩下的部分用一个括号整个“封装”起来。最简单的例子比方说我们以前学过的强制转换 (int) value这类的,但是我们现在稍微复杂一些。如下:
float (*h) (); 表示h是一个指向返回值为浮点类型的函数的指针,所以有
(float (*) () ) 表示一个“指向返回值为浮点类型的函数的指针”的类型转换 (这一步需要理解一下)
2、理解
有了上面的铺垫之后,我们就开始理解(*(void (*)())0)();这个语句。
1) (void (*)()) 里面的这个和铺垫的一样,是一个类型转换,表示一个“指向返回值为void类型的函数的指针”的类型转换。
2) (void (*)()) 0 这是表示将常数0转换为“指向返回值为void的函数指针”类型。
3) 有了1) 和 2)的理解之后,我们就明白,0是一个函数指针,它指向的函数的返回值类型为void,这样就比较好办了,那我们就按照使用指针变量的方法去理解它。我们使用一个指针变量的时候,前面会带一个*号,同理,对于这样一个函数指针,我们在调用它的时候。也类似地这么调用,就是(*(void (*)())0)();这就分析完了。
3、补充
这个例子主要运用在计算机地址方面,比方说arm芯片一上电的时候,是从0地址处开始执行程序。而要对各个状态字进行配置或者存入某个内存块的时候,处理的方法也都是把一个常数(也就是这个地址)转换成一个指针类型,然后对这个指针进行操作就能对该地址进行操作,这方面在嵌入式的配置方面见得比较多。
之前有一个功能挺复杂的驱动程序,其中有一条语句是这样的:
pos == IOFPGA_DO_DATA_ADDR(iofpga_if_get_dod_addr(), pfifo) ;
那我就进去看一下它的定义咯,如下:
#define IOFPGA_DO_DATA_ADDR(base, reg)((unsigned int)&(((struct st_iofpga_do_data *)((int)base))->reg))
右边的表达式我一看就知道在干嘛了(因为在此之前我看过标题的例子),我们看一下右边的表达式对这base和reg这两个形参做了什么事情,很明显嘛:
1)首先把base强制转换为int类型,注意,它是一个常数。
2)结合上面的例子分析,(struct st_iofpga_do_data *)((int)base) 和上面的例子用法一样,就是把一个常数转换成一个指针,这 里是转换成自定义的结构体指针。
3)((struct st_iofpga_do_data *)((int)base))->reg) 把这个常数转换成结构体指针之后,在这个结构体中找到传入的reg成员。
4) &(((struct st_iofpga_do_data *)((int)base))->reg)) 找到该结构体里面的这个成员之后,用&取其地址
5) (unsigned int)&(((struct st_iofpga_do_data *)((int)base))->reg) &取到这个成员的地址之后,强制转换为(unsigned int)类型
6)分析完毕....
结合对结构体的定义和传入的实参,不难理解这个函数的功能就是找到结构体特定成员的偏移地址。