9

作为回答另一个问题的一部分,我遇到了这样的一段代码,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 中是否允许?


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

4

7 回答 7

8

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

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

于 2010-05-24T06:45:46.530 回答
7

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

如果形式的类型说明符 struct-or-union identifier不是作为上述形式之一的一部分出现,并且没有其他标识符作为标记的声明是可见的,则它声明不完整的结构或联合类型,并将标识符声明为标记那种。

...和 ​​6.2.5 第 22 段:

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

于 2010-05-24T07:11:10.893 回答
2

第一种和第二种情况是明确定义的,因为指针的大小和对齐方式是已知的。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,您不能弄乱对象的内部结构。它是一种封装。

于 2010-05-24T06:55:28.290 回答
1

你必须给它命名。在这:

typedef struct {
    struct xyz *z;
} xyz;

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

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

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

于 2010-05-24T06:55:58.527 回答
1

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

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

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

在这种情况下,新struct X声明具有function-prototype 范围,它在原型的末尾结束。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类型。

于 2010-05-24T07:16:41.947 回答
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。第一行可以正常编译,但第二行会报错。那时,链表实现没有多大用处。

于 2010-05-24T06:56:44.577 回答
0

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

另一方面,当声明结构指针类型的变量或字段时,编译器必须分配足够的字节来保存指向结构的指针。 无论结构占用一字节还是一百万,指针总是需要相同数量的空间。 实际上,编译器可以将指向不完整类型的指针视为 void*,直到它获得有关其类型的更多信息,然后在发现更多信息后将其视为指向适当类型的指针。不完整类型指针与 void* 不太相似,因为 void* 可以做一些不完整类型不能做的事情(例如,如果 p1 是指向 struct s1 的指针,而 p2 是指向 struct 的指针s2,不能将 p1 分配给 p2),但是不能用指向不完整类型的指针做任何事情,而不能对 void* 做任何事情。基本上,从编译器的角度来看,指向不完整类型的指针是指针大小的字节块。它可以复制到或从其他类似的指针大小的字节块中复制,仅此而已。

于 2011-11-21T00:40:49.320 回答