【问题标题】:Passing foo(int *) as argument to foo(void*) in X将 foo(int *) 作为参数传递给 X 中的 foo(void*)
【发布时间】:2021-03-18 18:57:03
【问题描述】:

A 有一个功能:

void somefunc(void func(void *)) { ... }

void foo(int *num) {...} 

我试图将 foo 作为参数传递给 somefunc 但收到警告。 这样做的正确方法是什么?

【问题讨论】:

  • 你有一些不平衡的括号,这绝对是不正确的。发布minimal reproducible example 和警告。
  • void sumfunc(foo(X))
  • void somefunc(void (*func)(void *)) { ... } ?但是void *int * 之间的类型不匹配是个问题。如果你知道它可以在你的机器上工作,你可以强制转换:somefunc((void (*)(void *))foo) 但通常它是 UB,因为例如 void *int * 可能有不同的调用约定。 AFAIK 唯一可移植的方法是重写 foo 以期待 void *,或者编写一个辅助函数。
  • @NateEldredge 如果somefunc 在使用前将其转换回void (*)(int *) 也是安全的。
  • @dxiv,如果somefunc() 在您的控制之下并且倾向于假设参数确实是void (*)(int *),那么最好将其声明为参数类型首先。

标签: c function-pointers


【解决方案1】:

具有一个具有一个或多个void * 参数的函数的参数是实现某些类型泛型算法的常用方法。 (此答案假定此代码中的情况。存在其他可能性但不太可能。)funcsomefunc 的参数提供了一个回调函数,somefunc 用于在不知道其类型的情况下对数据进行操作。

请注意,虽然参数被声明为函数void func(void *),但它会自动调整为指向函数void (*func)(void *) 的指针。删除名称,我们有抽象类型,void (*)(void *)

您不能为void (*)(void *) 参数传递void (*)(int *) 参数,因为它们是不兼容的类型。并且您不应该尝试将 foo 参数强制转换为参数类型,因为 C 标准未定义使用转换后的指针调用 void (int *) 函数的行为。一个适当的解决方案是将指针传递给预期类型的​​函数。您可以为此创建一个新功能:

void FooWrapper(void *p) { foo(p); }

然后您可以将FooWrapper 传递给somefunc。当somefunc 调用FooWrapper 时,它会传递一个void *。当FooWrapper 调用foo 时,void * 将自动转换为int *。 (推测foo 是预先声明的,带有原型。)只要FooWrapper 传递的void * 起源于指向某些int 的指针,就定义了从void * 回到int * 的这种转换按照 C 标准,所以这是正确的代码。

或者,您可以修改foo 以采用void * 参数并在内部将其转换为int *,而不是创建包装器:

void foo(void *VoidNum)
{
    int *num = VoidNum;
    …
}

那么你可以将foo直接传递给somefunc

【讨论】:

  • "您不能为 void (*)(void *) 参数传递 void (*)(int *) 参数" 这取决于 somefunc 如何使用该参数。如果它将它转换回原始类型,那么它是合法的并且没问题。
  • @dxiv:不,无论somefunc 在内部做什么,调用都是未定义的。当foo 被传递时,它必须被转换为参数类型,C 2018 6.5.2.2 7 说就像通过赋值一样完成,但是转换违反了 6.5.16.1 1 中的赋值约束,所以行为没有定义。调用者可以使用强制转换对其进行转换,但是根据 C 6.5.2.2 9,在新类型中调用它的行为是未定义的。somefunc 可以使用自己的强制转换来反转转换,将函数作为其定义的类型调用,但第一段假定情况并非如此。
  • 将一种类型的函数指针转换为不同类型的函数指针并返回是无损的,6.3.2.3/8 明确允许。
  • @dxiv:没有把它扔回去。正如我在第一段中所写的,这不是典型的使用模式,并且假设不是这种情况。此外,您现在争论的是后面关于强制转换指针的句子可能是已定义行为的一部分。这与您的第一条评论不同,第一条评论是关于该段落的第一句话,并且在没有第一段假设的情况下仍然正确。
  • OP 的帖子中不存在somefunc 内部没有演员表的假设。例如this 之类的代码与提出的问题完全一致,并且合法且安全。
【解决方案2】:

我正在尝试将 foo 作为参数传递给 somefunc 但收到警告。

发生错误是因为somefunc 被声明为期望void (*)(void *) 类型的参数,但正在传递的参数foo 具有void (*)(int *) 类型。这两种类型不同且不兼容。

这样做的正确方法是什么?

函数指针可以在不同类型之间进行强制转换,所以下面的代码编译不会出错。

void bar()  { somefunc((void (*)(int *))foo); }

使用辅助typedef 使相同的代码更具可读性。

typedef void (*VOID_FUNC)(void *);

void bar()  { somefunc((VOID_FUNC)foo); }

重要的警告是,唯一somefunc 中合法/安全地使用func 参数是将其转换回原来的真实类型使用前的功能。来自语言标准中的相关段落6.3.2.3/8:“指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再转换回来;结果应与原始指针相等。 如果使用转换后的指针调用类型与引用类型不兼容的函数,则行为未定义。"

以下是正确用法的缩略示例。

extern int n;
void foo(int *num);

typedef void (*VOID_FUNC)(void *);
typedef void (*INT_FUNC)(int *);

void somefunc(VOID_FUNC func)  { ((INT_FUNC)func)(&n); }

void bar()  { somefunc((VOID_FUNC)foo); }

【讨论】:

  • 完整的代码示例是here
  • “重要的警告是,在 somefunc 中唯一合法/安全地使用 func 参数是在使用它之前将其转换回原始函数的真实类型。” - 当然,但如果somefunc 打算这样做,它会被定义为首先采用void(*)(int *)
  • @user2357112supportsMonica 当然,对于一种类型。但是somefunc 可能类似于我在第一条评论中链接的代码,并为日志记录/分析/等目的包装不同的调用。或者它可以是变体样式上下文中的辅助函数。或者任何数量的其他场景,其中涉及多种类型,所有这些都是预先知道的。
猜你喜欢
  • 2011-02-08
  • 2012-04-04
  • 2020-06-16
  • 2013-01-30
  • 2020-03-08
  • 1970-01-01
  • 1970-01-01
  • 2012-01-25
  • 2019-05-27
相关资源
最近更新 更多