[英]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.