C - 不兼容的指针类型

时间:2016-07-11 03:10:47

标签: c pointers struct initialization incompatibletypeerror

为什么以下代码会发出警告?

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;
                                            ^

3 个答案:

答案 0 :(得分:33)

它们是两种匿名结构类型(它们都没有标签)。所有这些结构类型(在单个翻译单元中)都是不同的 - 它们永远不是同一类型。添加标签!

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

  

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

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 的存在意味着这是另一种新类型。< / p>

如评论中所述,还有§6.2.7兼容类型和复合类型部分,其中说:

  

¶1......而且,两个结构,   union或在单独的翻译单元中声明的枚举类型如果它们是相容的   标签和成员满足以下要求:如果使用标签声明一个,则   其他声明应使用相同的标签声明。如果两者都在他们的任何地方完成   各个翻译单位,则适用以下附加要求:应有   是他们的成员之间的一对一的对应,使每对   相应的成员用兼容的类型声明;如果这对中的一个成员是   用对齐说明符声明,另一个用等效对齐声明   符;如果该对的一个成员用名称声明,则声明另一个成员   同名。对于两个结构,相应的成员应在中宣布   同样的顺序。对于两个结构或联合,相应的位域应具有相同的位   宽度。

粗略地说,如果两个翻译单元中的类型的定义(认为'源文件'加上包含的标题)是相同的,那么它们指的是相同的类型。感谢老天爷!否则,您可能无法使用标准I / O库,以及其他一些细节。

答案 1 :(得分:13)

变量&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中描述了声明符。 此外,如果它们的标签和成员满足以下要求,则在单独的翻译单元中声明的两个结构,联合或枚举类型是兼容的:如果使用标记声明一个,则使用相同的方式声明另一个标签。如果两者都在其各自的翻译单元内的任何地方完成,则以下附加要求适用:其成员之间应存在一对一的对应关系,以便每对相应的成员被宣布为兼容类型;如果使用对齐说明符声明该对中的一个成员,则使用等效的对齐说明符声明另一个成员;如果该对的一个成员使用名称声明,则另一个成员使用相同的名称声明。对于两个结构,相应的成员应按相同的顺序声明。对于两个结构或联合,相应的位域应具有相同的宽度。对于两个枚举,相应的成员应具有相同的值。

答案 2 :(得分:2)

最初设计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之类的编译器选项时才能正常工作,这些选项会禁用比使用不太激进的编译器时所需的更多优化。