看了C陷阱與缺陷,第一個給我震撼的就是理解函數(shù)聲明了,下面是我的理解。
1.理解函數(shù)聲明
為了模擬開機啟動時的情形,我們必須設計出一個C語句,以顯示調(diào)用位于地址0的子例程。調(diào)用語句如下:
(*(void (*)())0)();
膽顫了吧?首先我們從函數(shù)的聲明說起:有如下一個函數(shù)
void func(){...}
那么,要想聲明一個函數(shù)指針,指向這類函數(shù),怎樣聲明呢?如下:
void (*pf)();
那么,想要將一個值轉(zhuǎn)換成一個指針,指向這類函數(shù),怎么強制轉(zhuǎn)換呢?如下:
(void (*)())value
現(xiàn)在value被轉(zhuǎn)換成了一個指向函數(shù)的指針,怎么調(diào)用它呢?如下:
((void) (*)()value)();
或 (*(void) (*)()value)();
或 (******(void) (*)value)();
神奇吧,看了上面的函數(shù)調(diào)用方式!其實你完全可以這樣調(diào)用函數(shù):
func();
(*func)();
(********func)();
沒有任何問題!
對于初學的時候,最容易頭暈的就是指針符*到底是什么東西?它是變量的一部分?還是聲明類型的一部分?其實,仔細回憶C中標識符的定義規(guī)則就知道,指針符*必須是類型聲明的一部分,因為變量的聲明不能含有指針符號,否則是一個非法的變量!
2.C語言中的聲明
對比簡單類型的強制轉(zhuǎn)換我們就可以更加明白上面的強制轉(zhuǎn)換,簡單類型定義如下:
int a;
char b;
b=(char)a;
int *a;
char *b;
b=(int *)a;
其實,在各種指針之間轉(zhuǎn)換很少見,最常見的就是將void指針轉(zhuǎn)換成各種指針,用過malloc族函數(shù)嗎?
a=(int *)malloc(sizeof(int));
由于聲明符和表達式類似,所以你可以這樣聲明
int ((a)); //將a聲明為一個int型值
int *func(),(*fp)();
前者是一個函數(shù),后者是一個指針,所以千萬不要對指針定義成函數(shù)了,分不清概念的時候最容易這樣
int (*fp)(){
/*do something*/
}
顯然,這里將一個指針定義成了一個函數(shù)!這是任何編譯器都不能容忍的。
結合簡單變量的類型,出去聲明中的變量,得到的就是這個變量的類型,如float a,那么要將一個變量轉(zhuǎn)換成a的類型,那么只需要
(float)value;即可,同理:
float *b,除去變量b,得到b的類型是(float *)。
float (*fp)();除去變量fp,得到的類型是float (*)(),所以要將變量轉(zhuǎn)換成fp類型的值時,只需要(float (*)())value,即可!這樣value即被轉(zhuǎn)換成一個函數(shù)指針了,也就像最前面的例子中那樣!
如果,更復雜,有聲明如下:
float * (*fp)(); //返回一個float指針
其實,這也是唬人的,同理可知:強制轉(zhuǎn)換類型為(float * (*)())value;
3.其他考量
看到第一個例子時,可能想,能不能這樣子:(*0)();呢?
不行,因為0是一個數(shù)字,而*必須要操作一個指針,而且對于要調(diào)用的函數(shù)是void型的,所以這個指針應該轉(zhuǎn)換成相應的類型,所以需要將0轉(zhuǎn)成一個指向void返回值的參數(shù)為空的函數(shù)的指針。
最后,linux內(nèi)核中的信號處理函數(shù)定義如下:
void (*signal(int,void (*)(int)))(int);
首先,將上面的函數(shù)聲明看成這樣void (*p)(int);可知,p是一個函數(shù),所以signal函數(shù)的返回類型為一個函數(shù)指針,指向的函數(shù)類型是
void (*)(int);使用typedef可以簡化上面signal函數(shù)的聲明
typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);
解釋一下:signal是一個返回函數(shù)指針的函數(shù),返回的指針指向的函數(shù)類型是void (*)(int);而signal的參數(shù)一個是int,另一個是一個函數(shù),類型為void (*)(int),剛好就是最初聲明的樣子。
摘自C陷阱和缺陷一書,但被網(wǎng)上一個自認為是高手的挫人改成這個樣子,湊合著看吧
聲明與函數(shù) 有一段程序存儲在起始地址為0的一段內(nèi)存上,如果我們想要調(diào)用這段程序,請問該如何去做?
答案 答案是(*(void (*)( ) )0)( )??雌饋泶_實令人頭大,那好,讓我們知難而上,從兩個不同的途徑來詳細分析這個問題。
答案分析:從尾到頭
首先,最基本的函數(shù)聲明:void function (paramList); 最基本的函數(shù)調(diào)用:function(paramList); 鑒于問題中的函數(shù)沒有參數(shù),函數(shù)調(diào)用可簡化為 function();
其次,根據(jù)問題描述,可以知道0是這個函數(shù)的入口地址,也就是說,0是一個函數(shù)的指針。使用函數(shù)指針的函數(shù)聲明形式是:void (*pFunction)(),相應的調(diào)用形式是: (*pFunction)(),則問題中的函數(shù)調(diào)用可以寫作:(*0)( )。
第三,大家知道,函數(shù)指針變量不能是一個常數(shù),因此上式中的0必須要被轉(zhuǎn)化為函數(shù)指針。我們先來研究一下,對于使用函數(shù)指針的函數(shù):比如void(*pFunction)( ),函數(shù)指針變量的原型是什么? 這個問題很簡單,pFunction函數(shù)指針原型是( void (*)( )),即去掉變量名,清晰起見,整個加上()號。所以將0強制轉(zhuǎn)換為一個返回值為void,參數(shù)為空的函數(shù)指針如下:( void (*)( ) )。OK,結合2)和3)的分析,結果出來了,那就是:(*(void (*)( ) )0)( ) 。
答案分析:從頭到尾理解答案
(void (*)( )) ,是一個返回值為void,參數(shù)為空的函數(shù)指針原型.(void (*)())0,把0轉(zhuǎn)變成一個返回值為void,參數(shù)為空的函數(shù)指針,指針指向的地址為0. *(void (*)())0,前面加上*表示整個是一個返回值為void的函數(shù)的名字 (*(void (*)( ))0)( ),這當然就是一個函數(shù)了。我們可以使用typedef清晰聲明如下: typedef void (*pFun)( ); 這樣函數(shù)變?yōu)?(*(pFun)0 )( );
問題:三個聲明的分析 對聲明進行分析,最根本的方法還是類比替換法,從那些最基本的聲明上進行類比,簡化,從而進行理解,下面通過分析三個例子,來具體闡述如何使用這種方法。
#1:int* (*a[5])(int, char*);
首先看到標識符名a,"[]"優(yōu)先級大于"*",a與"[5]"先結合。所以a是一個數(shù)組,這個數(shù)組有5個元素,每一個元素都是一個指針,指針指向"(int, char*)",很明顯,指向的是一個函數(shù),這個函數(shù)參數(shù)是"int, char*",返回值是"int*"。OK,結束了一個。:)
#2:void (*b[10]) (void (*)());
b是一個數(shù)組,這個數(shù)組有10個元素,每一個元素都是一個指針,指針指向一個函數(shù),函數(shù)參數(shù)是"void (*)()"【注10】,返回值是"void"。完畢! 注意:這個參數(shù)又是一個指針,指向一個函數(shù),函數(shù)參數(shù)為空,返回值是"void"。
#3. double(*)() (*pa)[9]; pa是一個指針,指針指向一個數(shù)組,這個數(shù)組有9個元素,每一個元素都是"double(*)()"(也即一個函數(shù)指針,指向一個函數(shù),這個函數(shù)的參數(shù)為空,返回值是"double")。
(自己注:#3看起來有點難理解,如果把double(*)()換成int, 變成int (*pa)[9]這樣就不難理解了)