【问题标题】:Passing `int (*)(char const*)` where `int (*)(char*)` is expected在需要 `int (*)(char*)` 的地方传递 `int (*)(char const*)`
【发布时间】:2016-12-01 14:31:43
【问题描述】:

我有一个函数指针,其函数被声明为期望 char * 参数。在其中,我想保存一个指向声明为采用 char const* 参数的函数的指针。

我想我可以使用包装器或演员表。 强制转换看起来更直接,但我可以合法地调用这种函数指针强制转换的结果吗?

示例代码如下:

static int write_a(char * X){
    return 0;
}

static int write_b(char const* X){
    return 0;
}
static int wrapped_write_b(char * X){
    return write_b(X);
}

typedef int (*write_fn)(char * );

write_fn a = write_a;
write_fn b = wrapped_write_b;
write_fn b1 = (write_fn)write_b; //is b1 legally callable?

【问题讨论】:

  • 根据this 它是UB(我认为)。但是,由于 const 仅在构建时而不是运行时强制执行,我认为这不会成为问题。
  • @Danh:演员阵容完全合法。您提供的链接与此演员阵容完全无关。

标签: c pointers function-pointers


【解决方案1】:

这是未定义的行为 - 只有当类型兼容时,您才能使用指针调用另一种类型的函数 (6.3.2.3/8):

指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应与原始指针比较。如果转换后的指针用于调用类型与引用类型不兼容的函数,则行为未定义。

如果(简化版)两个函数具有相同的返回和参数,则它们具有兼容的类型(6.7.6.3,Semantics/15):

对于要兼容的两个函数类型,两者都应指定兼容的返回类型。146) 此外,参数类型列表(如果两者都存在)应在参数数量和省略号终止符的使用方面达成一致;对应的参数应该有兼容的类型。

const char *char * 不兼容(6.7.6.1, Semantics/2):

对于要兼容的两种指针类型,两者都应具有相同的限定,并且都应是指向兼容类型的指针。

由于const char *char * 的限定不同,它们不兼容,并且通过b 调用write_b 是未定义的行为。

【讨论】:

  • 没问题。谢谢。
  • "你可以使用指针来调用函数..." 但是OP的代码并没有通过b1调用函数!
【解决方案2】:

严格来说是不允许的。

pointer-to-somethingpointer-to-qualified-something 不兼容。因为pointer-to-qualified-something 不是pointer-to-something 的限定类型

同样适用于

pointer-to-function-accepting-something

pointer-to-function-accepting-qualified-something.

这个可以通过C116.2.7兼容类型找到:

如果它们的类型相同,则两种类型具有兼容的类型。 确定两种类型是否兼容的附加规则是 在6.7.2 中描述类型说明符,在6.7.3 中描述类型 限定词...

其中6.7.3 是相关部分。它说

对于要兼容的两个限定类型,两者都应具有兼容类型的相同限定版本;

转换章节6.3.2.3与此不矛盾:

指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针 键入并再次返回;结果应与原始指针比较。 如果转换 指针用于调用类型与引用类型不兼容的函数, 行为未定义。

编辑

正如 Holt 的回答中所述,6.7.6.3/15 中明确描述了两个函数的兼容性。


我仍然认为包装函数是最好的解决方案。问题的根源在于write_a 不是常量正确的。如果您无法更改该函数,请在其周围编写一个包装器。

【讨论】:

  • 演员表本身是完全允许和合法的。 “不允许”是通过b1 调用函数。但是OP的代码没有通过b1进行任何调用。
  • 另外,这里的“严格别名”这句话是无关紧要的。 aliasing(和strict aliasing)的概念只适用于指向数据的指针,不适用于指向函数的指针。
  • @AnT 关于你的第一句话,这正是粗体字所说的。您对严格别名是正确的,该标准仅提及对象。固定。
【解决方案3】:

write_fn b1 = (write_fn)write_b; //这样合法吗?

什么合法吗?

此转换中涉及的函数指针类型不兼容。

然而,将函数指针转换为不兼容的函数指针类型是完全合法的。但是,对这种强制转换的指针唯一能做的就是将其转换回原始类型。语言规范保证这种往返转换将保留原始指针值。这就是为什么我们可以将void (*)(void) 用作函数指针的“通用存储类型”(如void * 用于数据指针)。它可以用于存储任何类型的函数指针(但不能用于调用函数)。要执行这种指针存储(和检索),我们必须使用显式转换,就像您的代码中的转换一样。没有什么违法的。

同时,尝试通过b1 调用函数会导致未定义的行为,特别是因为指针类型与实际函数类型不兼容。

在您的问题中,您明确声明您要“保存”指向该函数的指针。只要我们只谈论“保存”(存储)指针,您的代码就完美无缺。

【讨论】:

  • 好点。通过该指针调用函数是我应该明确提到的目标。
  • 建议添加 ref: C11 §6.3.2.3 8 "指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再转换回来;结果应比较等于原始指针。”
猜你喜欢
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2015-01-05
  • 2014-04-22
相关资源
最近更新 更多