如何在结构中引用未定义的类型是合法的?

作为回答另一个问题的一部分,我遇到了一段像这样的代码,gcc编译时没有抱怨。

typedef struct { struct xyz *z; } xyz; int main (void) { return 0; } 

这是我一直用来构造指向自己的类型的方法(例如,链接列表),但我一直认为你必须命名结构,以便你可以使用自引用。 换句话说,您无法在结构中使用xyz *z ,因为此时typedef尚未完成。

但是这个特定的样本没有命名结构,它仍然编译。 我原本认为编译器中有一些黑魔法会自动翻译上面的代码,因为结构和typedef名称是相同的。

但是这个小小的美也可以:

 typedef struct { struct NOTHING_LIKE_xyz *z; } xyz; 

我在这里想念的是什么? 这似乎是一个明显的违规,因为在任何地方都没有定义任何struct NOTHING_LIKE_xyz类型。

当我从指针更改为实际类型时,我得到预期的错误:

 typedef struct { struct NOTHING_LIKE_xyz z; } xyz; qqq.c:2: error: field `z' has incomplete type 

此外,当我删除struct ,我得到一个错误(解决parse error before "NOTHING ... )。

ISO C允许这样做吗?


更新: struct NOSUCHTYPE *variable; 也编译所以它不仅仅是内部结构似乎是有效的。 我在c99标准中找不到任何允许结构指针宽大的东西。

您所追求的C99标准的部分是6.7.2.3,第7段:

如果forms为struct-or-union identifier的类型说明struct-or-union identifier不是作为上述表单之一的一部分而发生,并且没有其他标识符作为标记声明可见,则它声明一个不完整的结构或联合类型,并声明标识符作为该类型的标记。

……和6.2.5第22段:

未知内容的结构或联合类型(如6.7.2.3中所述)是不完整类型。 对于该类型的所有声明,通过在稍后的同一范围内声明相同的结构或union标记及其定义内容,它已完成。

正如警告在第二种情况下所说, struct NOTHING_LIKE_xyz是一个不完整的类型 ,如void或未知大小的数组。 不完整类型只能作为指向的类型出现,但允许作为结构的最后一个成员的未知大小的数组例外,在这种情况下使结构本身成为不完整的类型。 下面的代码不能取消引用任何指向不完整类型的指针(有充分理由)。

不完整的类型可以在C中提供一些数据类型的封装… http://www.ibm.com/developerworks/library/pa-ctypes1/中的相应段落似乎是一个很好的解释。

第一和第二种情况是明确定义的,因为指针的大小和对齐是已知的。 C编译器只需要大小和对齐信息来定义结构。

第三种情况无效,因为实际结构的大小未知。

但请注意,对于第一种情况是合乎逻辑的,您需要为结构命名:

 // vvv typedef struct xyz { struct xyz *z; } xyz; 

否则外部结构和*z将被视为两种不同的结构。


第二种情况有一个流行的用例,称为“不透明指针”(pimpl) 。 例如,您可以将包装器结构定义为

  typedef struct { struct X_impl* impl; } X; // usually just: typedef struct X_impl* X; int baz(X x); 

在标题中,然后在其中一个.c

  #include "header.h" struct X_impl { int foo; int bar[123]; ... }; int baz(X x) { return x.impl->foo; } 

优点是那个.c ,你不能搞乱对象的内部。 这是一种封装。

你必须命名它。 在这:

 typedef struct { struct xyz *z; } xyz; 

将无法指向自身,因为z指的是某个完整的其他类型,而不是您刚刚定义的未命名结构。 试试这个:

 int main() { xyz me1; xyz me2; me1.z = &me2; // this will not compile } 

您将收到有关不兼容类型的错误。

嗯……我只能说你先前的假设不正确。 每次使用struct X构造(单独或作为较大声明的一部分)时,它都被解释为带有struct标签X的struct类型的声明。 它可以是先前声明的结构类型的重新声明。 或者,它可以是结构类型的第一个声明。 新标记在其出现的范围内声明。 在您的具体示例中,它恰好是一个文件范围(因为C语言没有“类范围”,就像在C ++中一样)。

这种行为的更有趣的例子是当声明出现在函数原型中时:

 void foo(struct X *p); // assuming `struct X` has not been declared before 

在这种情况下,新的struct X声明具有函数原型范围 ,该范围原型的结尾处结束。 如果稍后声明文件范围struct X

 struct X; 

并尝试将struct X类型的指针传递给上述函数,编译器将为您提供有关非匹配指针类型的诊断信息

 struct X *p = 0; foo(p); // different pointer types for argument and parameter 

这也立即意味着在以下声明中

 void foo(struct X *p); void bar(struct X *p); void baz(struct X *p); 

每个struct X声明都是一个不同类型的声明,每个声明都有自己的函数原型范围。

但是如果你预先将struct X声明为

 struct X; void foo(struct X *p); void bar(struct X *p); void baz(struct X *p); 

所有函数原型中的所有struct X引用都将引用相同的 previosly声明的struct X类型。

我也想知道这件事。 原来struct NOTHING_LIKE_xyz * z是前向声明struct NOTHING_LIKE_xyz 。 作为一个复杂的例子,

 typedef struct { struct foo * bar; int j; } foo; struct foo { int i; }; void foobar(foo * f) { f->bar->i; f->bar->j; } 

这里f->bar指的是struct foo类型,而不是typedef struct { ... } foo 。 第一行编译正常,但第二行会出错。 那么对于链表实现没什么用处。

声明结构类型的变量或字段时,编译器必须分配足够的字节来保存该结构。 由于结构可能需要一个字节,或者可能需要数千个,因此编译器无法知道需要分配多少空间。 有些语言使用多遍编译器,它能够在一次传递中找出结构的大小,并在以后的传递中为它分配空间; 因为C被设计为允许单通道编译,所以这是不可能的。 因此,C禁止声明不完整结构类型的变量或字段。

另一方面,当声明指向结构类型的变量或字段时,编译器必须分配足够的字节来保存指向结构的指针。 无论结构是占用一个字节还是一百万,指针总是需要相同的空间量。 实际上,编译器可以将指向不完整类型的指针作为void *,直到它获得有关其类型的更多信息,然后在找到更多相关类型后将其视为指向相应类型的指针。 不完整类型的指针与void *不太相似,因为可以使用void *做一些不完全类型的事情(例如,如果p1是指向struct s1的指针,而p2是指向struct的指针s2,一个人不能将p1分配给p2)但是一个人不能用一个指向不完整类型的指针做任何事情,而这个指针无法做到无效*。 基本上,从编译器的角度来看,指向不完整类型的指针是指针大小的字节blob。 它可以复制到其他类似指针大小的字节blob或从其他类似的指针复制,但就是这样。 编译器可以生成代码来执行此操作,而无需知道对于指针大小的字节blob将执行什么操作。