[英]Function pointer with a generic pointer parameter
我有一個數據結構,為每個節點存儲一個通用的void *
,並在適當的時間將其轉換為正確的類型。 然后,在該對象的清除函數中,我想提供一個回調,以便也可以“清除”該通用對象。
struct foo {
void *data;
// ...
};
void foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data)) {
data_cleanup(foo->data);
// ...
}
// ...
void bar_cleanup(void *data) {
struct bar *bar = (struct bar *)data;
// ...
}
這工作正常,但是如果bar_cleanup
的簽名直接引用bar
而不是void *
,我更喜歡:
void bar_cleanup(struct bar *bar)
當然,按原樣替換該代碼會產生“參數類型不匹配”警告。 如果沒有類似的方法來完成相同的清理任務,是否有任何方法可以直接實現我要執行的操作?
您現在正在做的是正確的處理方式。 您希望使用指針來清理特定類型的函數,這違反了C11(和C99,甚至可能是C90,盡管我尚未正式檢查C90)中的(嚴格)規則。
[§6.3]轉換
§6.3.2.3指針
¶8指向一種類型的函數的指針可以轉換為指向另一種類型的函數的指針,然后再返回。 結果應等於原始指針。 如果使用轉換后的指針來調用其類型與引用的類型不兼容的函數,則該行為是不確定的。
您現有的代碼是:
struct foo {
void *data;
// ...
};
void foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data)) {
data_cleanup(foo->data);
// ...
}
void bar_cleanup(void *data) {
struct bar *bar = (struct bar *)data;
// ...
}
此代碼是干凈的,並遵守規則。 指向bar
清除功能的指針具有簽名void (*)(void *)
,與foo_cleanup()
使用的指針匹配。 bar_cleanup()
中的bar_cleanup()
是可選的,但是顯式的。 即使您省略了強制轉換符號,該轉換仍會發生,因為C會自動從void *
轉換為struct bar *
。
如果嘗試使用清除功能:
void bar_cleanup(struct bar *bar);
您必須撥打相當於以下電話的電話:
struct foo foo37;
…code initializing foo37…
foo_cleanup(&foo37, (void (*)(void *))bar_cleanup);
這會將函數的類型強制為其他指針類型。 除非foo_cleanup()
的代碼以某種方式(如何?)知道指針需要使用帶有簽名void (*)(struct bar *)
的函數,並在調用cleanup函數之前對其進行更改,否則它將運行與6.3節中的規則相違背的行為。 .2.3。
foo_cleanup(struct foo *foo, void (*data_cleanup)(void *data))
{
(*data_cleanup)(foo->data); // Undefined behaviour
if (data->…)
(*(void (*)(struct bar *))data_cleanup)(foo->data); // OK, but…
…
}
無條件調用是錯誤的,因為實函數指針的類型與參數類型要求的類型不同。 條件調用是干凈的,因為它在調用函數之前將指針轉換回其實型。 (這是C;從void *
到struct bar *
的轉換是自動有效的。)。 但是,必須知道在foo_cleanup()
函數中將指針轉換為什么的功能,這使使用指針指向函數的目的擺在首位。 還不清楚foo_cleanup()
如何確定哪種類型轉換正確,並且如果添加了新類型,則必須再次更改代碼以支持新類型。
所有這些都意味着使用void bar_cleanup(struct bar *bar)
的解決方案實際上是不可接受的。
如果您遵循標准規定的嚴格規則,並且仍然想調用void bar_cleanup(struct bar *)
,則必須編寫令人毛骨悚然的,不可維護的,不靈活的代碼。
如果您想要絕對可靠的代碼,則將遵循以下規則並保留現有代碼( void bar_cleanup(void *data)
)。 它具有避免痛苦的強制轉換的有益副作用-函數指針的轉換不是很漂亮-並且只要調用代碼知道,無論struct foo
的data
成員中存儲了多少種不同的指針類型, foo_cleanup()
函數都保持不變。這是正確的類型(如果調用代碼不知道,無論如何都是“放棄希望進入這里的所有人”的情況)。
實際上,這有多嚴重? 實際上,此刻您可能會擺脫它。 但是它正在調用未定義的行為,並且編譯器一直渴望識別和利用未定義的行為來“優化”它們生成的代碼。 您可以在不進行foo_cleanup()
情況下foo_cleanup()
,但是您正冒着風險,可以通過保留當前代碼來簡單而輕松地避免這種風險。
請注意,這適用於傳遞到標准庫中的qsort()
或bsearch()
比較器函數。 這些函數應編寫為帶有兩個const void *
參數並返回一個int
。 否則,將違反6.3.2.3節的規定。 在其他受人尊敬的C語言書籍中,有一些示例未遵守這些嚴格的規則。
typedef void (*CLEANUP_FUNC)(void *);
void foo_cleanup(struct foo *foo, CLEANUP_FUNC *data_cleanup) {
data_cleanup(foo->data);
// ...
}
void bar_cleanup(struct bar *data) {
// ...
}
foo_cleanup(foo, (CLEANUP_FUNC *)bar_cleanup);
以下是一個完整的示例,其中具有通用堆棧Stack
和一些Foo
類型的元素。
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
// ---
typedef void (STACK_DEALLOCATOR)(void *);
typedef struct Stack {
STACK_DEALLOCATOR *element_deallocator;
size_t num_elements;
size_t num_allocated;
void **elements;
} Stack;
Stack *Stack_New(STACK_DEALLOCATOR *element_deallocator) {
Stack *this = malloc(sizeof(Stack));
if (this == NULL)
goto ERROR;
this->element_deallocator = element_deallocator;
this->num_elements = 0;
this->num_allocated = 4;
this->elements = malloc(sizeof(void *) * this->num_allocated);
if (this->elements == NULL)
goto ERROR2;
return this;
ERROR2: free(this);
ERROR: return NULL;
}
int Stack_Push(Stack *this, void *element) {
if (this->num_elements == this->num_allocated) {
// ...
}
this->elements[ this->num_elements++ ] = element;
return 1;
}
void Stack_Destroy(Stack *this) {
void **element = this->elements;
for (size_t i=this->num_elements; i--; ) {
this->element_deallocator(*(element++));
}
free(this->elements);
free(this);
}
// ---
typedef struct Foo {
int data;
// ....
} Foo;
Foo *Foo_New(int data) {
Foo *this = malloc(sizeof(Foo));
if (this == NULL)
return NULL;
this->data = data;
return this;
}
void Foo_Destroy(Foo *this) {
free(this);
}
// ---
int main(void) {
Stack *stack = Stack_New((STACK_DEALLOCATOR *)Foo_Destroy);
if (stack == NULL) {
perror("Stack_New");
goto ERROR;
}
Foo *foo = Foo_New(123);
if (foo == NULL) {
perror("Foo_New");
goto ERROR2;
}
if (!Stack_Push(stack, foo)) {
perror("Stack_Push");
goto ERROR3;
}
Stack_Destroy(stack);
return 0;
ERROR3: Foo_Destroy(foo);
ERROR2: Stack_Destroy(stack);
ERROR: return 1;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.