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

时间:2010-05-24 06:40:04

标签: c struct undefined language-lawyer

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

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

这是我一直用来构建指向自己的类型的方法(例如,链接列表),但我一直认为你必须命名这个结构,所以你可以使用self-参考。换句话说,您无法在结构中使用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标准中找不到允许结构指针宽大的任何内容。

7 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:6)

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

  

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

......和6.2.5第22段:

  

未知的结构或联合类型   内容(如6.7.2.3中所述)是   不完整的类型。它完成了,   对于那种类型的所有声明,通过   宣布相同的结构或联合   标签及其后面的定义内容   相同的范围。

答案 2 :(得分:2)

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

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

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

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

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


第二个案例有一个称为"opaque pointer" (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,你不能搞砸对象的内部。这是一种封装。

答案 3 :(得分:1)

您必须为其命名。在这:

typedef struct {
    struct xyz *z;
} xyz;

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

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

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

答案 4 :(得分:1)

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

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

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

在这种情况下,新的struct X声明具有 function-prototype scope ,它在原型的结尾处结束。如果您稍后声明文件范围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个引用都将引用相同的预先声明的struct X类型。

答案 5 :(得分:0)

我也想知道这件事。事实证明,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。第一行编译正常,但第二行会出错。那么对于链表实现没什么用处。

答案 6 :(得分:0)

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

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