c陷阱與缺陷學習筆記 第二章 語法陷阱

2021-09-10 04:47:51 字數 3806 閱讀 5765

任何c變數的宣告都由兩部分組成:型別以及一組類似表示式的宣告符。宣告符從表面上看與表示式有些類似。對它求值應該返回乙個宣告中給定型別的結果。最簡單的宣告符就是單個符號

float f,g;
這個宣告的含義就是:當對其求值時,表示式f和g的型別為浮點型型別。因為宣告符與表示式的相似,所以我們也可以在宣告符中任意使用括號。

float ((f));
這個宣告的含義是:當對其求值的時候,((f))的型別是浮點型別,由此可知,f也是浮點型別。

同樣的邏輯也適用於函式和指標型別的宣告,例如:

float ff();
這個宣告的含義是:表示式ff()求值結果是乙個浮點數,也就是說,ff是乙個返回值為浮點型別的函式,類似的

float *pf;
這個宣告的含義是 pf是乙個浮點數,也就是說pf是乙個指向浮點數的指標

以上這些形式在宣告中還可以組合起來,就像在表示式中進行組合一樣,因此

float * g(),(*h)();
表示*g()與(h)()是浮點表示式,因為()結合優先順序高於,*g()也就是*(g()),g是乙個函式,該函式的返回值型別為指向浮點數的指標。同理,可以得出h是乙個函式指標,h所指向函式的返回值為浮點型別。

一旦我們知道了如何宣告乙個給定型別的變數,那麼該型別的型別轉換符就很容易得到了:只需要把宣告中的變數名和宣告末尾的分號去掉,再將剩餘的部分用乙個括號整個封裝i起來。例如,

float (*h)();
表示h是乙個指向返回值為浮點型別的函式的指標

(float(*)())
表示乙個指向返回值為浮點型別的函式的指標的型別轉換符

現在我們來分析(*(void(*)())0)()

第一布,極愛的那個fp是乙個函式指標,那麼如何呼叫fp所指向的函式呢?呼叫方法如下:

(*fp)();
因為fp是乙個函式指標,那麼*fp就是該指標所指向的函式,所以(*fp)()就是掉頭用該函式的方法。標準准許程式設計師把上面的式子簡寫為fp()。但是一定要記住這只是一種簡寫形式在表示式(*fp)(),兩側的括號非常重要,因為()的優先順序高於星號,如果沒有括號,那麼*fp()*(fp())的含義完全一樣

現在就是要找到乙個恰當的表示式來替換fp,我們在第二部來解決這個問題。如果c編譯器能夠理解我們大腦中對於型別的認識,那麼我們應該這樣寫

(*0)();
但是上面的式子並不能生效,因為型號必須要乙個指標來做運算元。而且這個指標應該是乙個函式指標,這樣星號作用後的結果才能作為函式被呼叫。所以必須對0進行型別轉換,轉換後的型別可以描述為指向返回值為void型別的函式的指標

如果fp是乙個指向返回值為void型別的函式的指標,那麼(*fp)()的值為void,fp的宣告為;

void (*fp)();
因此,我們可以用下式來完成呼叫儲存位置為0的子例程

void (*fp)();

(*fp)();

這種寫法的代價是多宣告了乙個啞變數

但是我們一旦知道了怎麼宣告乙個變數,也就自然知道如何對乙個常數進行型別轉換,將其轉型為該變數的型別;只需要在變數宣告中將變數名去掉就可以了。

因此,將常數0轉型為指向返回值為void的函式的指標型別,可以這樣寫

(void (*)()) 0
因此,我們可以用(void(*)())0來替換fp,可以得到

(*(void(*)())0)();
也可以使用typedef來使得表訴更加清晰

typedef void (*funcptr)();

(*(funcptr)0)();

還有例如我們在考慮signal庫函式 ,在包括該函式的從、編譯器實現中,signal函式接收兩個引數,乙個是代表需要**獲的特定signal的整數值,返回值型別為void。

一般情況下,程式設計師不主動宣告signal函式,而是直接使用系統標頭檔案的signal.h中的宣告,那麼在signal.h中,signal函式是如何宣告的

首先,我們從使用者定義的訊號處理函式開始考慮

void signfunc(int n)

void (*pfunc)(int);

很簡單,上面這段**宣告了乙個函式fucntion和乙個函式指標pfunc, 它指向的函式就是乙個具有void返回值,int引數的函式。如果將function函式的位址給pfunc指標,可以簡單的通過下面兩種賦值:

pfunc = function;

或者 pfunc = &function;

通過指標呼叫該函式,也有兩種方法:

pfunc(5); 或 (*pfunc)(5);

我們看一下賦值語句,pfunc = function; 但有時候可能是乙個常數0x8999940, 它恰好也表示乙個安全的與function相同的函式,如何將這個數值賦給pfunc呢?顯然我們需要強制型別轉換,應該將該常數轉換成什麼型別呢?這就是問題的關鍵!

在void (*pfunc)(int)語句裡面,只有pfunc是變數名稱,那麼剩餘的部分,void(*)(int),就是我們需要的轉換型別。因此,新的賦值語句是:

pfunc = (void (*)(int)) 0x8999940;

賦值完成後,就可以通過pfunc(5); 或 (*pfunc)(5);呼叫相應的函式了。

如果理解了上面的內容,我們就可以解釋void (*signal(int, void (*)(int)))(int)這個相對複雜的問題了

返回函式指標的函式聲名

現在我們先拋開上面那個複雜的定義,先看一下下面的需求1) 定義乙個函式;2) 該函式具有以下特點,兩個引數,返回值是函式指標,並且乙個引數也是函式指標。假如返回值和引數函式指標同為void (*)(int); 另乙個函式引數是int型。該函式定義名稱為my_func。

根據需求我們可以很容易定義出這種函式:

typedef void (*handler)(int); // 引數函式和返回函式定義

handler my_func(int, handler);

突然需求中又不讓使用typedef,這就是早期c語言不支援typedef的情況,那麼如何定義這種函式呢?

我們假如說my_func的返回值是int,是不是它的定義可以這麼寫:

int my_func(int, void (*)(int));

也就是說,my_func(int, void (*)(int))就是乙個int型資料。現在將int換成乙個函式,也就是

void (*)(int) my_func)(int, void (*)(int);

這樣一種定義,顯然這種語法不支援,那麼,實際是如何表示呢?回過頭來,我們先看看函式指標的宣告格式

void (*pfunc)(int);

其中pfunc 等價於 void (*)(int)。現在在看看上面的格式,是不是很相識,對了,pfunc就是my_func(int, void (*)(int))。現在如果將兩者代替一下是不是就成了這種格式:

void (*my_func(int, void(*)(int)))(int)

如果將my_func換成signal,是不是就是我們文章開始提到的那個複雜聲名?現在是不是明白了,原來如此啊,它是乙個返回函式指標的的函式聲名!

《再讀》第二章 語法「陷阱」

看了第一章後發現第一節還是那麼深奧啊 呵呵,今天看了第二章,雖然後點麻煩,不過 如果掌握了作者所說的關鍵的話,其實也不是特別難了,但還是有些宣告的方法覺得很怪異,也 許是因為沒有這樣用過,也沒聽說過吧。今天難得的在書中找到了一些錯誤啊 呵呵,這算是一 個不大不小的收穫吧。1.對函式宣告的理解。在c語...

c陷阱與缺陷 語法「陷阱

語法 陷阱 語法是在詞法的基礎上延伸,它教怎樣組合成宣告,表示式,語句和程式 理解函式宣告 任何c變數宣告都是由 型別 一組類似表示式的宣告符 float f 乙個簡單的變數宣告 float f 這是乙個函式宣告,其返回值 float型別的函式 float f 這是乙個指向float型別的指標 fl...

《C陷阱與缺陷》學習筆記(二)

第四章 連線 聯結器並不理解c語言,然而它能理解機器語言和記憶體布局。作者強調聯結器並不能處理連線時和c語言相關的一些錯誤,如果c語言提供了lint,要善加利用。每個外部物件都必須在程式某個地方進行定義。這就意味著如果乙個程式中包括了語句extern int a 就應該在別的某個地方包括語句int ...