C - 不兼容的指针类型

28

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

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

4
@Jonathan Leffler的回答是正确的。任何不同类型指针之间的转换都会引发警告。不幸的是,由于这两个结构体都没有名称且不同,您无法在它们之间进行强制类型转换。因此,您应该事先声明您的结构体。 - Radnyx
1
除了其他答案之外,值得注意的是可以消除警告。只需进行中间转换为void*struct {int x; int y;} *test_ptr = (void*)&test; - ach
如果你希望它们是相同的类型,只需定义一次类型。 - Keith Thompson
@Radnyx:这并不是那么简单。不兼容的 指针类型之间的转换大多数情况下都会违反约束条件,需要进行诊断(gcc 默认会发出警告,这是有效的,但它也可能是致命错误)。类型可以相容而不是同一种类型;另外 void* 和例如 int* 是不兼容的,但是它们可以被赋予彼此(存在隐式转换)。 - Keith Thompson
3个回答

34

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

标准中相关的句子在§6.7.2.1 结构体和联合体规范中:

 

¶8在结构或联合体规范中存在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的存在意味着这是另一种新类型。

正如评论中所述,还有第§6.2.7节的Compatible type and composite type,其中写道:

 

¶1...此外,如果它们的标记和成员满足以下要求,则在单独的翻译单元中声明的两种结构、联合或枚举类型是兼容的:如果其中一个使用标记进行声明,则另一个应使用相同的标记进行声明。如果两个在各自的翻译单元中的任何地方都已完成,则应满足以下附加要求:它们之间应该有一对一的对应关系,使得每一对相应的成员都声明为兼容类型;如果一对中的一个成员声明了对齐说明符,则另一个成员声明了与之等效的对齐说明符;如果一对中的一个成员声明了名称,则另一个成员声明了相同的名称。对于两个结构体,相应的成员应按照相同的顺序声明。对于两个结构体或联合,相应的位字段应具有相同的宽度。

粗略地说,如果两个翻译单元(类似于“源文件”加上包含的头文件)中类型定义相同,则它们指向相同的类型。感谢上帝!否则,你不能使用标准 I/O 库,还有其他细节问题。


这并不完全正确,不同翻译单元中定义的两个匿名结构体可以被兼容。 - 2501
1
@2501:同意(§6.2.7 兼容类型和复合类型)...我正在尝试找到标准在哪里规定了我在单个TU中所说的内容。 - Jonathan Leffler

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节中描述。而且,如果在单独的翻译单元中声明了两个结构体、联合体或枚举类型,则它们是兼容的,前提条件是它们的标记和成员满足以下要求:如果一个使用标记进行了声明,则另一个也必须使用相同的标记进行声明。如果两者在各自的翻译单元内的任何地方都已完成,则还需要满足以下附加要求:它们的成员之间应该存在一一对应关系,使得每一对相应的成员都用兼容类型进行声明;如果一对成员中的一个被声明为对齐指示符,则另一个应该被声明为等效的对齐指示符;如果一对成员中的一个带有名称,则另一个应该带有相同的名称。对于两个结构体,相应的成员应该按相同的顺序进行声明。对于两个结构体或联合体,相应的位字段应该具有相同的宽度。对于两个枚举类型,相应的成员应该具有相同的值。


那似乎表明它应该能够工作,对吗?两者都没有标签,它们的成员是相同的。 - zneak
@zneak 注意第一个加粗引用:单独的翻译单元。 - 2501
请参考§6.7.2.1 ¶8,了解单个TU中的不同类型。 - Jonathan Leffler
我认为格式应该按照我编辑的方式进行。这样读起来更好(在我看来)。 - edmz
@black 我同意参考文献应该放在底部。 - 2501

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)时才能正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接