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。





pointers