简体   繁体   English

具有通用指针参数的函数指针

[英]Function pointer with a generic pointer parameter

I have an data structure that stores a generic void * for each node which gets casted to the correct type at the appropriate time. 我有一个数据结构,为每个节点存储一个通用的void * ,并在适当的时间将其转换为正确的类型。 In the cleanup function for this object, I would then like to provide a callback so that this generic object can too be "cleaned up." 然后,在该对象的清除函数中,我想提供一个回调,以便也可以“清除”该通用对象。

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;
    // ...
}

This works fine, however I would prefer if the signature of bar_cleanup referred to bar directly, rather than void * : 这工作正常,但是如果bar_cleanup的签名直接引用bar而不是void * ,我更喜欢:

void bar_cleanup(struct bar *bar)

Of course, replacing that code as is creates "parameter type mismatch" warnings. 当然,按原样替换该代码会产生“参数类型不匹配”警告。 Is there any way to achieve directly what I am trying to do, if not a similar method of achieving the same cleanup task? 如果没有类似的方法来完成相同的清理任务,是否有任何方法可以直接实现我要执行的操作?

What you are doing now is the correct way to deal with it. 您现在正在做的是正确的处理方式。 Your desire to use pointers to cleanup functions with a specific type runs foul of the (strict) rules in C11 (and C99, and probably C90 though I've not formally checked C90). 您希望使用指针来清理特定类型的函数,这违反了C11(和C99,甚至可能是C90,尽管我尚未正式检查C90)中的(严格)规则。

[§6.3] Conversions [§6.3]转换

§6.3.2.3 Pointers §6.3.2.3指针

¶8 A pointer to a function of one type may be converted to a pointer to a function of another type and back again; ¶8指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回。 the result shall compare equal to the original pointer. 结果应等于原始指针。 If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined. 如果使用转换后的指针来调用其类型与引用的类型不兼容的函数,则该行为是不确定的。


Your existing code is: 您现有的代码是:

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;
    // ...
}

This code is clean and obeys the rules. 此代码是干净的,并遵守规则。 The pointer to the bar cleanup function has the signature void (*)(void *) which matches the pointer used by foo_cleanup() . 指向bar清除功能的指针具有签名void (*)(void *) ,与foo_cleanup()使用的指针匹配。 The cast in bar_cleanup() is optional but explicit. bar_cleanup()中的bar_cleanup()是可选的,但是显式的。 Even if you omit the cast notation, that conversion will occur as C automatically converts from void * to struct bar * . 即使您省略了强制转换符号,该转换仍会发生,因为C会自动从void *转换为struct bar *

If you try to use the cleanup function: 如果尝试使用清除功能:

void bar_cleanup(struct bar *bar);

you would have to make a call equivalent to: 您必须拨打相当于以下电话的电话:

struct foo foo37;
…code initializing foo37…
foo_cleanup(&foo37, (void (*)(void *))bar_cleanup);

This coerces the type of the function to a different pointer type. 这会将函数的类型强制为其他指针类型。 Unless the code inside foo_cleanup() knows somehow (how?) that the pointer needs to use function with the signature void (*)(struct bar *) and changes it before calling the cleanup function, it runs afoul of the rule in §6.3.2.3. 除非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…
    …
}

The unconditional call is wrong because the types of the real function pointer and the one claimed by the parameter type are different. 无条件调用是错误的,因为实函数指针的类型与参数类型要求的类型不同。 The conditional call is clean because it casts the pointer back to its real type before calling the function. 条件调用是干净的,因为它在调用函数之前将指针转换回其实型。 (This is C; the conversion from void * to struct bar * is automatic and valid.) . (这是C;从void *struct bar *的转换是自动有效的。)。 However, having to know what to convert the pointer-to-function to in the foo_cleanup() function defeats the purpose of using a pointer-to-function in the first place. 但是,必须知道在foo_cleanup()函数中将指针转换为什么的功能,这使使用指针指向函数的目的摆在首位。 It also isn't clear how foo_cleanup() does determine which cast is correct, and if you add a new type, you have to change the code again to support the new type. 还不清楚foo_cleanup()如何确定哪种类型转换正确,并且如果添加了新类型,则必须再次更改代码以支持新类型。

All of this means that a solution using void bar_cleanup(struct bar *bar) is not really acceptable. 所有这些都意味着使用void bar_cleanup(struct bar *bar)的解决方案实际上是不可接受的。

If you follow the strict rules laid down by the standard and still want to call void bar_cleanup(struct bar *) , you have to write gruesome, non-maintainable, inflexible code. 如果您遵循标准规定的严格规则,并且仍然想调用void bar_cleanup(struct bar *) ,则必须编写令人毛骨悚然的,不可维护的,不灵活的代码。

If you want absolutely reliable code, you will follow these rules and keep your existing code ( void bar_cleanup(void *data) ). 如果您想要绝对可靠的代码,则将遵循以下规则并保留现有代码( void bar_cleanup(void *data) )。 It has the beneficial side-effect of avoiding painful casts — function pointer casts are not pretty — and leaves the foo_cleanup() function unchanged regardless of how many different pointer types are stored in the data member of struct foo as long as the calling code knows which is the correct type (and if the calling code doesn't know, it's a case of "Abandon Hope All Ye Who Enter Here" anyway). 它具有避免痛苦的强制转换的有益副作用-函数指针的转换不是很漂亮-并且只要调用代码知道,无论struct foodata成员中存储了多少种不同的指针类型, foo_cleanup()函数都保持不变。这是正确的类型(如果调用代码不知道,无论如何都是“放弃希望进入这里的所有人”的情况)。

In practice, how serious a problem is this? 实际上,这有多严重? Actually, you'll probably get away with it at the moment. 实际上,此刻您可能会摆脱它。 But it is invoking undefined behaviour and compilers are ever eager to identify and exploit undefined behaviour to 'optimize' the code they generate. 但是它正在调用未定义的行为,并且编译器一直渴望识别和利用未定义的行为来“优化”它们生成的代码。 You can do as you want without the cast in foo_cleanup() , but you are taking risks which can be simply and painlessly avoided by keeping your current code. 您可以在不进行foo_cleanup()情况下foo_cleanup() ,但是您正冒着风险,可以通过保留当前代码来简单而轻松地避免这种风险。

Note that this applies to comparator functions passed to qsort() or bsearch() in the standard library. 请注意,这适用于传递到标准库中的qsort()bsearch()比较器函数。 Those functions should be written to take two const void * parameters and return an int . 这些函数应编写为带有两个const void *参数并返回一个int Doing otherwise runs foul of §6.3.2.3. 否则,将违反6.3.2.3节的规定。 There are examples in otherwise respected C books that do not keep to these strict rules. 在其他受人尊敬的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);

The following is a full example featuring generic stack Stack and some elements of type Foo . 以下是一个完整的示例,其中具有通用堆栈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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM