将 void*(*)(void*) 强制转换为 void(*)(void)

4
作为任务的一部分,我正在尝试创建一个类似于pthread的用户级线程库。
为了处理线程之间的上下文切换,我正在使用'swapcontext'函数。在使用它之前,我必须使用'makecontext'函数创建一个上下文。'makecontext'期望一个带有返回类型void和参数类型(void)的函数指针。
而线程函数必须是void* thread_func (void*)类型。
是否有一种方法进行类型转换?或者还有其他方式在用户级别上进行上下文切换吗?

2
你需要的是将 void*(*)(void*) 转换为 void(*)(void) 的类型转换吗? - Ali1S232
@Chunk-e-Yamani,我现在已经编辑了问题,以正确说明标题中的转换。 - user4815162342
3个回答

7

将函数地址强制转换为不同原型并通过生成的指针调用具有不兼容原型的函数是非法的:

void *my_callback(void *arg) { ... }

void (*broken)(void *) = (void (*)(void *)) my_callback;
broken(some_arg);   // incorrect, my_callback returns a `void *`

你可以将自己的回调函数传递给makecontext,该回调函数将调用thread_func并忽略其返回值。仅用于调用其他函数的小型函数有时被称为trampoline
/* return type is compatible with the prototype of the callback received
   by makecontext; simply calls the real callback */
static void trampoline(int cb, int arg)
{
  void *(*real_cb)(void *) = (void *(*)(void *)) cb;
  void *real_arg = arg;
  real_cb(real_arg);
}

int my_pthread_create(void *(*cb)(void *), void *arg)
{
  ucontext_t *ucp;
  ...
  /* For brevity treating `void *` as the same size as `int` -
     DO NOT USE AS-IS.
     makecontext exposes an annoyingly inconvenient API that only
     accepts int arguments; correct code would deconstruct each
     pointer into two ints (on architectures where pointer is
     larger than int) and reconstruct them in the trampoline. */
  makecontext(ucp, trampoline, 2, (int) cb, (int) arg);
  ...
}

如果你想获得额外积分,你可以修改蹦床(trampoline)来将回调函数返回的void *值存储在栈中,并使用相应的方法替代pthread_join()来获取该值。


3
准确地说,类型转换是完全可以的。然而,通过错误的指针类型调用函数是未定义的行为。 - Oliver Charlesworth
2
在C语言中,void (*)()并不是一个没有参数的函数 - 它是一种旧式的函数,具有固定但未指定数量的参数,且不提供原型。因此,调用带有参数的void (*)()时无需进行强制转换。 - caf
@caf 你是对的,我以为这在C99中被删除了,但它仅仅已经被弃用。我会更新答案。 - user4815162342

4
原则上,您可以将任何类型的指针转换为任何其他类型的指针,但对于函数指针,我强烈建议不要这样做。
如果在错误传递后调用thread_func,它将期望栈上的参数,而这些参数将不会被提供。更糟糕的是,thread_func会将返回值写入不应该写入的地方,从而破坏您的堆栈。
一个解决方案是使用适当类型的自己的函数来包装调用。

2
+1 对于用户4815162342省略的关于栈的解释 - Rerito

0

您可以像变量一样对函数指针进行类型转换。语法可能有些笨拙,但肯定是可行的(是否明智另当别论)。

然而,在这种情况下,这可能不是您想要做的事情。从swapcontext的手册页面中可以看到:

在调用makecontext()之前,调用者必须为此上下文分配一个新堆栈,并将其地址分配给ucp->uc_stack。

您的线程函数需要一个参数。通过创建的堆栈将该参数传递到新上下文中。传递给makecontext()的函数可以是一个包装器函数,它从堆栈中检索值并将其作为参数传递给线程函数。仅进行类型转换无法提供一种将参数中的数据传递到新上下文中的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接