c - pointer教學 - pointers中文
函數指針轉換為不同的簽名 (5)
C標準明確不支持將函數指針轉換為NULL。 你受編譯器編寫者的擺佈。 它可以在很多編譯器上正常工作。
對於函數指針來說,沒有等價的NULL或void *是C的一大煩惱之一。
如果你真的想要你的代碼是防彈的,你可以聲明你自己的空值,但是每個函數類型都需要一個。 例如,
void void_int_NULL(int n) { (void)n; abort(); }
然後你可以測試
if (my_thing->func_a != void_int_NULL) my_thing->func_a(99);
醜陋,有罪?
我使用函數指針的結構來實現不同後端的接口。 簽名是非常不同的,但返回值幾乎都是void,void *或int。
struct my_interface {
void (*func_a)(int i);
void *(*func_b)(const char *bla);
...
int (*func_z)(char foo);
};
但並不要求後端支持每個接口函數的函數。 所以我有兩種可能性,第一種選擇是在每次調用之前檢查指針是否不等於NULL。 我不太喜歡這個,因為它的可讀性,因為我擔心性能的影響(但是我沒有測試過)。 另一種選擇是有一個虛擬函數,在極少數情況下,一個接口函數不存在。
因此,我需要每個簽名虛擬函數,我想知道是否有可能只有一個不同的返回值。 並把它投給給定的簽名。
#include <stdio.h>
int nothing(void) {return 0;}
typedef int (*cb_t)(int);
int main(void)
{
cb_t func;
int i;
func = (cb_t) nothing;
i = func(1);
printf("%d\n", i);
return 0;
}
我用gcc測試了這個代碼,它工作。 但理智嗎? 或者它可以破壞堆棧還是會導致其他問題?
編輯:感謝所有的答案,我現在了解了很多關於調用約定,稍後進一步閱讀。 現在已經對引擎蓋下發生的事情有了更好的了解。
你確實有造成堆棧損壞的風險。 話雖如此,如果你用extern "C"
鏈接(和/或__cdecl
取決於你的編譯器)聲明函數,你也許可以逃避這一點。 這跟類似於printf()
這樣的函數的方式可以根據調用者的判斷採用不定數量的參數。
在目前的情況下,這是否正常工作還取決於你使用的確切的編譯器選項。 如果你正在使用MSVC,那麼debug和release編譯選項可能會有很大的不同。
應該沒問題。 由於調用者在調用後負責清理堆棧,所以不應該在堆棧上留下任何額外的東西。 被調用者(在這種情況下沒有()())是可以的,因為它不會嘗試使用堆棧上的任何參數。
編輯:這不假設cdecl調用約定,這通常是默認的C.
我懷疑你會得到一個未定義的行為。
你可以指定(用適當的強制轉換)一個指向另一個指針的指針來使用不同的簽名,但是當你調用它時會發生奇怪的事情。
你的nothing()
函數不需要任何參數,對於編譯器來說,這可能意味著他可以優化堆棧的使用,因為那裡沒有參數。 但是在這裡你用一個論點來說,那是一個意想不到的情況,它可能會崩潰。
我在標準中找不到適當的點,但是我記得它說你可以強制轉換函數指針,但是當你調用結果函數時,你必須使用正確的原型,否則行為是不確定的。
作為一個方面說明,你不應該比較函數指針和數據指針(如NULL),因為指針可能屬於不同的地址空間。 在C99標準中有一個附錄允許這個特定的情況,但是我不認為它被廣泛實現。 也就是說,在只有一個地址空間的體系結構中,將函數指針轉換為數據指針或將其與NULL進行比較,通常會起作用。
除非您使用特定於實現的/特定於平台的東西強制執行正確的調用約定,否則這將不起作用。 對於某些調用約定,被調用的函數負責清理堆棧,因此他們必須知道被推送的內容。
我會去檢查NULL,然後調用 - 我無法想像它會對性能有任何影響。
計算機可以像任何事情一樣快地檢查NULL。