【问题标题】:Is this forward declaration of a function pointer valid in C?这个函数指针的前向声明在 C 中是否有效?
【发布时间】:2016-08-14 04:46:31
【问题描述】:

我正在尝试找出以下前向声明在 ANSI-C 中是否有效:

第一个文件:

extern void * fptr;   // opaque forward declaration.
int main (void) {
  fptr = NULL;        // set the function pointer to NULL
}

第二个文件:

typedef int (*fptr_t)(int);
fptr_t fptr;         // real declaration of the function pointer

对我来说,这应该是无效的,因为 fptr 如果用两种不同的类型声明,但 gccclang 都没有给出任何警告。

我会对 C11 标准的精确点更感兴趣,这些点可以得出结论为什么它有效(或无效)。


编辑:在 C11 标准中,6.2.7:2 说:

引用同一对象或函数的所有声明都应具有 兼容类型;否则,行为未定义。

但我找不到如何确定void* 是否与fptr_t 兼容。

【问题讨论】:

  • 如果两个声明永远不会一起出现在同一个编译单元中,您将不会出现编译时错误。链接目标文件时,如果可以解析符号引用,即使类型不兼容,也不会出现错误。这与在一个文件中将变量声明为 int 而在另一个文件中声明为 float 没有什么不同。
  • 我了解编译器看不出问题。但是,如果您在一个文件中将变量声明为 int 而在另一个文件中声明为 float,您会遇到麻烦,不是吗?这个案子怎么办?
  • 据我了解的标准,如果类型是“兼容的”就可以了。但在这种情况下,它们是否存在?
  • 你是对的,但在这种情况下,我相信它是特定于实现的,它还取决于你如何使用变量。如果指针类型之间的分配不会导致表示形式发生任何变化,它只会执行您想要的操作。但这不是你应该依赖的东西。
  • @LPs 不,如果编译器在同一个编译单元中看到这两个声明,它会报错。 链接器 是会或不会发出警告的位置。您将链接器与编译器混淆了。它们非常不同。

标签: c function-pointers


【解决方案1】:

它应该可以工作,但它是无效的。在第一个文件中,您声明标识符fptr 将在另一个编译单元中定义,并且它将是void *。在第二个文件中,您定义了标识符,但它现在是指向函数的指针。编译后的文件一般不会保留对象的类型(只保留地址)所以:

  • 编译器不知道其他源声明了不同的类型并且不能发出任何警告
  • 链接器不需要控制,所以标准不需要警告,常见的实现不控制类型,所以也没有警告

在常见的实现中,所有指针都具有相同的表示形式,将指向函数的指针转换为指向 void 的指针不会改变表示形式,因此别名会给出预期的结果。

但它仍然是 未定义的行为(*) 每个标准,因为 6.2.5 类型 § 27 声明(强调我的):

指向 void 的指针应具有与 指向字符类型的指针。39) 类似地,指向合格或不合格版本的指针 兼容类型应具有相同的表示和对齐要求。所有指向结构类型的指针都应具有相同的表示和对齐要求 作为彼此。所有指向联合类型的指针都应具有相同的表示形式和 对齐要求彼此。 指向其他类型的指针不必相同 表示或对齐要求

(*) 因为它没有被通用实现测试,UB 被当前版本的编译器检测和优化掉的风险很小。但我永远不会在生产代码中这样做......

【讨论】:

  • 如果架构为每个逻辑空间(代码、数据等)使用虚拟地址空间,则可以有一个指向函数的指针等于 A,而指向对象的指针等于到 A,都指不同的地址空间。 (哈佛建筑)。
  • @Jean-BaptisteYunès 如果指针不指向同一个对象,则它们不会比较相等。
  • 不可以!可以在架构中产生差异。例如,JUMP A 可能会跳转到与 LOAD A 加载不同的位置。两条不同的指令可能使用不同的地址空间来解释相同的。所以你的两个指针可能看起来包含相同的值,但这个值的解释可能不同......
  • @Jean-BaptisteYunès 您只能使用 == 运算符有意义地比较指向不同对象的指针,这就是我之前的评论所指的内容。您在谈论(我假设)指针的实际位的值,对于指向不同对象的两个指针,这可能是相同的,但这超出了 C 的范围,即 C 没有定义位值是。
  • 我不喜欢第一句话:“既正确又无效。” 只是无效。当然,它可能适用于某些系统,但这并不意味着它是正确的。
【解决方案2】:

void * 是一个与函数类型指针不同的对象类型指针。它们不是兼容的类型。

但是:

便携性问题J.5.7。函数指针转换

  1. 指向对象或 void 的指针可能 转换为指向函数的指针,允许将数据作为 功能(6.5.4)。

  2. 指向函数的指针可以转换为指针 到对象或无效,允许检查功能或 修改(例如,由调试器) (6.5.4)。

那么为什么不完全隐藏模块中的指针,并通过外部函数来操作它呢?这将避免别名问题。

【讨论】:

  • 它不在我的某些代码中,所以我无法更改它。我只能向开发人员报告,但我想确定这是一个问题。
  • 嗯,这是参考书中的问题,但是有很多平台可以正常工作......生活有时是妥协的问题。如果该代码未嵌入某些飞机控制中,而仅嵌入在我的糖果迷恋游戏中...
【解决方案3】:

不,这是无效的,因为本质上您是将常规指针(NULLvoid*)存储到实际上是函数指针的内存位置。您只是将其隐藏在编译器中,链接器并不关心,但归根结底,您有未定义的行为,因为这两种指针类型不一定兼容。当然,它可能适用于许多系统,但可能不是全部。

有关函数指针与 void 指针的更多信息,请参见此处:can void* be used to store function pointers? - 虽然这与您所介绍的情况略有不同,但答案仍然相关。

【讨论】:

  • 感谢您提供有趣的链接。
【解决方案4】:

C99:

6.2.7 兼容类型和复合类型

第 2 条:

引用同一对象或函数的所有声明都应具有兼容的类型;否则,行为未定义。

6.7.5.1 指针声明符

第 2 条:

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

如果不进一步挖掘标准,很容易看出void 和函数不是兼容的类型。

我敢打赌这在 C11 中不会改变。长期以来,C 一直隐式支持不同的代码和数据空间以及代码和数据指针的不同大小和表示形式,如果要删除此功能并将语言限制在较小的机器子集上可用,那将是很奇怪的。因此,请谨慎投票。有证据更好。

【讨论】:

  • C11也是一样。如果它们的类型相同,您可能想要添加两个类型是兼容的(有一些例外,但在这种情况下不是)。
  • 我同意你的结果(指向 void 的指针和指向函数的指针不兼容),但不同意原因:voidchar 不是兼容类型,但 void *char * 是。
  • @SergeBallesta AFAIK, void* 被有意引入为与char* 兼容,这是一个例外。但是,对于指向函数的指针和指向数据(或 void)的指针,从来没有出现过这样的异常。
  • 值得注意的是,相当多的实现确实保证void *可以安全地与函数指针相互转换。例如,POSIX 做出了这种保证。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-20
  • 2020-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多