【问题标题】:C - Incompatible Pointer TypeC - 不兼容的指针类型
【发布时间】:2016-11-12 21:48:24
【问题描述】:

为什么下面的代码会给出警告?

int main(void)
{
    struct {int x; int y;} test = {42, 1337};
    struct {int x; int y;} *test_ptr = &test;
}

结果:

warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
         struct {int x; int y;} *test_ptr = &test;
                                            ^

【问题讨论】:

  • @Jonathan Leffler 的回答是正确的。不同类型指针之间的任何转换都会引发该警告。不幸的是,由于这两个结构都是未命名且不同的,因此您不能在它们之间进行转换。这就是为什么你应该事先声明你的结构。
  • 除了其他答案,值得注意的是可以摆脱警告。只需对void*进行中间转换:struct {int x; int y;} *test_ptr = (void*)&test;
  • 如果你希望它们是相同的类型,定义类型once
  • @Radnyx:事情没那么简单。 incompatible 指针类型之间的转换(在大多数但不是所有情况下)是违反约束的,需要诊断。 (gcc 默认发出警告,这是有效的,但可能是致命错误)。类型可以兼容而不是相同的类型;还有void* 和例如int* 不兼容,但可以相互分配(存在隐式转换)。

标签: c pointers struct initialization incompatibletypeerror


【解决方案1】:

它们是两种匿名结构类型(它们都没有标签)。所有此类结构类型(在单个翻译单元中)都是不同的——它们绝不是相同的类型。添加标签!

标准中的相关句子在§6.7.2.1 结构和联合说明符

¶8 struct-declaration-liststruct-or-union-specifier 中的存在声明了一个新类型, 在翻译单元内。

struct-declaration-list指的是类型中{}之间的材料。

这意味着在您的代码中,有两种不同的类型,每个struct { … } 一种。这两种类型是分开的;您不能正式将一种类型的值分配给另一种类型,也不能创建指针等。事实上,您不能在分号之后再次引用这些类型。

这意味着你可以:

int main(void)
{
    struct {int x; int y;} test = {42, 1337}, *tp = &test;
    struct {int x; int y;} result, *result_ptr;
    result_ptr = &result;
    …
}

现在testtp指的是同一个类型(一个是结构,一个是指向结构的指针),同样resultresult_ptr指的是同一个类型,初始化和赋值是很好,但是这两种类型是不同的。尚不清楚您是否创建了任一类型的复合文字——您必须编写 (struct {int x; int y;}){.y = 9, .x = 8},但 struct-declaration-list 的存在意味着这是另一种新类型。

如 cmets 中所述,还有第 §6.2.7 节 兼容类型和复合类型,其中说:

¶1 …此外,两个结构, 在单独的翻译单元中声明的联合或枚举类型是兼容的,如果它们的 标签和成员满足以下要求:如果使用标签声明,则 其他应使用相同的标签声明。如果两者都在其范围内的任何地方完成 相应的翻译单位,则适用以下附加要求:应 是它们的成员之间的一一对应关系,使得每一对 相应的成员被声明为兼容的类型;如果一对中的一个成员是 使用对齐说明符声明,另一个使用等效对齐方式声明 说明符;如果该对中的一个成员用名称声明,则另一个成员被声明 同名。对于两个结构,相应的成员应在 相同的顺序。对于两个结构或联合,相应的位字段应具有相同的 宽度。

粗略地说,如果两个翻译单元中的类型定义(想想“源文件”加上包含的标题)相同,那么它们指的是相同的类型。感谢老天爷!否则,除了其他次要细节外,您无法让标准 I/O 库正常工作。

【讨论】:

  • 这并不完全正确,两个定义在不同翻译单元中的匿名结构可以兼容。
  • @2501:同意(第 6.2.7 节兼容类型和复合类型)...我试图找到标准在单个 TU 中所说的内容。
【解决方案2】:

变量&testtest_ptr 是匿名结构,具有不同的类型。

在同一翻译单元中定义的匿名结构永远不是兼容类型1,因为标准没有为同一翻译单元中的两个结构类型定义定义兼容性。

要编译你的代码,你可以这样做:

struct {int x; int y;} test = {42, 1337} , *test_ptr;
test_ptr = &test;

1(引自:ISO:IEC 9899:201X 6.2.7 兼容类型和复合类型1)
如果它们的类型相同,则两种类型具有兼容的类型。用于确定两种类型是否兼容的附加规则在 6.7.2 中描述了类型说明符,在 6.7.3 中描述了类型限定符,在 6.7.6 中描述了声明符。 此外,在单独的翻译单元中声明的两个结构、联合或枚举类型是兼容的,如果它们的标签和成员满足以下要求:如果一个用标签声明,另一个应该用相同的标签声明标签。如果两者都在各自翻译单元内的任何地方完成,则适用以下附加要求:它们的成员之间应存在一一对应关系,使得每对对应的成员都声明为兼容类型;如果该对的一个成员使用对齐说明符声明,则另一个成员使用等效的对齐说明符声明;如果该对中的一个成员声明了一个名称,则另一个成员声明为相同的名称。对于两个结构,相应的成员应以相同的顺序声明。对于两个结构或联合,相应的位域应具有相同的宽度。对于两个枚举,对应的成员应该具有相同的值。

【讨论】:

  • 这似乎说它应该工作,不是吗?两者都没有标签,并且它们的成员相同。
  • @zneak 注意第一个粗体引号:单独的翻译单元。
  • 针对单个 TU 中的不同类型尝试 §6.7.2.1 ¶8。
  • 我认为格式应该是我编辑它的方式。它读起来好多了(IMO)。
  • @black 我同意引用应该在底部。
【解决方案3】:

C 最初的设计目的是使指向具有部分或完全相同布局的结构的指针可以互换使用以访问公共部分,并且 C89 之前为结构成员实现单独命名空间的语言版本通常保留使用的能力指针在类型转换、通过 void 的转换等的帮助下可互换。虽然编译器在不同大小的数组之前插入不同数量的填充是合法的,但大多数编译器指定它们执行布局而不这样做,这意味着可以轻松编写一个函数,该函数将接受指向以下任一对象的指针,或任何其他类似声明的对象(大小为 4、5、24601 等)

struct { int size; int foo[2]; } my_two_foos = {2, {1,2} };
struct { int size; int foo[3]; } my_three_foos = {3, {4,5,6} };

由于不要求实现对布局提供任何保证,从而使此类构造变得不可或缺,因此标准的作者拒绝强制编译器承认任何布局兼容性的概念,因为那些具有这种能力的编译器是必不可少的(例如像上述结构将以一致的方式布置的那些)已经支持它,并且没有理由相信他们不会继续这样做,无论标准是否强制要求。是否应该强制执行某项功能或保证的驱动因素不是成本是否会超过可以廉价且轻松地支持该功能或保证的平台上的收益,而是支持最昂贵和最不实用的平台上的成本是否这些相同平台上的好处会超过

不幸的是,编译器编写者忽略了这样一个事实,即该标准只定义了实现“兼容”所必需的内容,并没有定义哪些特性可以使某个特定平台的编译器成为一个好的编译器,并且作为一个因此,他们越来越积极地寻找借口来忽略平台上的先例,这些平台几十年来一直以最低成本支持行为。因此,依赖于过去常见行为的代码可能只有在使用像 -fno-strict-aliasing 这样的编译器选项时才能正常工作,这些选项比使用不那么激进的编译器时需要的优化要多得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    相关资源
    最近更新 更多