C - 不兼容的指针类型

C - Incompatible Pointer Type

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

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

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

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

¶8 The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit.

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

这意味着在您的代码中,有两种不同的类型,每种类型 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 的存在意味着这是另一个新的类型。

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

¶1 … Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name. For two structures, corresponding members shall be declared in the same order. For two structures or unions, corresponding bit-fields shall have the same widths.

粗略地说,如果两个翻译单元中类型的定义(想想'source files'加上包含的headers)相同,那么它们指的是相同的类型。感谢老天爷!否则,您将无法使用标准 I/O 库以及其他次要细节。

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

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

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

上的好处将超过

不幸的是,编译器编写者忽略了这样一个事实,即标准仅定义了一个实现成为 "compliant" 所必需的条件,而没有定义哪些特性使某些东西成为给定平台的良好编译器,因此,他们越来越积极地寻找借口来忽视平台上的先例,在这些平台上,数十年来以最低成本支持行为。因此,依赖于过去常见行为的代码可能只有在使用像 -fno-strict-aliasing 这样的编译器选项时才能正常工作,这些选项禁用了比使用较不积极的编译器时所需的更多的优化。