理解 (*(void (*)())0)()
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)分析完畢....
結合對結構體的定義和傳入的實參,不難理解這個函式的功能就是找到結構體特定成員的偏移地址。