作为回答另一个问题的一部分,我遇到了一段像这样的代码,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中提供一些数据类型的封装…
第一和第二种情况是明确定义的,因为指针的大小和对齐是已知的。 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将执行什么操作。
以上就是c/c++开发分享如何在结构中引用未定义的类型是合法的?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/545679.html