让我们说我们在C或C ++中定义了两个结构(我在C和C ++中得到相同的结果,我认为规则是相同的,告诉我它们是不是)。
具有不完整类型的值成员的一个:
struct abc{
int data;
struct nonexsitnow next; //error: field ‘next’ has incomplete type, makes sense since "struct nonexsitnow" hasn't been declared or defined.
};
和一个指针成员不完整的类型:
struct abc{
int data;
struct nonexsitnow *next; //pass?!
};
为什么第二个定义没有导致任何问题?它使用的struct nonexsitnow
尚未创建!
我在下面的答案和评论中总结了这张表,希望他们能够正确并且有助于详细说明。
正如@ArneVogel所提到的,struct Foo p;
和struct Foo *p;
都是Foo
的隐式声明,而struct Foo;
明确地完成了这项工作(感谢@John Bollinger)。这对c几乎没有任何意义,但对c++和行为有所不同:
In a situation where struct Foo is:
_______________________________________________________
| | undeclared | declared but | defined |
| | | not defined | |
-------------------------------------------------------
| | C | C++ | C | C++ | C | C++ |
-------------------------------------------------------
|struct Foo p; | × | × | × | × | √ | √ |
|struct Foo* p; | √ | √ | √ | √ | √ | √ |
| Foo p; | × | × | × | × | × | √ |
| Foo* p; | × | × | × | √ | × | √ |
答案 0 :(得分:35)
为什么第二个结构定义不会导致任何问题?
因为它是一个指针。即使指针指向的类型不完整,编译器也知道指针的大小。
答案 1 :(得分:8)
编译器需要struct / class的大小,以便知道在实例化这种类型时必须分配多少内存。
在给定平台上,对于作为结构或类类型的任何类型T,sizeof(T*)
将始终返回相同的值。
这就是为什么你可以使用指向前向声明类型的指针而没有错误。当然,为了访问由这样的指针指向的对象的内容或者取消引用它,必须提供定义。但是,您可以为此类指针赋值(只要在类型兼容性方面允许它)。
一个重要的事实:
在C中,您通常使用所谓的“C风格转换”,通常可以执行指针分配,无论类型如何(您有责任确保正确的行为并满足对齐要求)。
但是,在C ++中,是否可以在不完整类型之间进行转换取决于转换的类型。考虑两种多态类型:
class A; // forward declaration only
class B; // forward declaration only, actually inherits from A
A* aptr;
B* bptr;
bptr = (B*)(aptr); // ok
bptr = dynamic_cast<B*>(aptr); // error
如果编译器无法访问转换中涉及的类型定义(执行运行时检查所必需的),则 dynamic_cast<>
将失败。示例:Ideone。
答案 2 :(得分:6)
首先,您需要了解类型对于&#34;不完整&#34;的含义。 C用这种方式定义:
在翻译单元内的各个点处,对象类型可以是 不完整(缺乏足够的信息来确定。的大小 那种类型的对象)或完整的(有足够的信息)。
(C2011,6.2.5/1)
请注意,类型完整性是声明的范围和可见性的函数,而不是类型的固有特征。翻译单元中的某个类型可能不完整,并且在不同的点完成。
然而,
指针类型可以从函数类型或对象类型派生, 称为引用类型。 [...] 指针类型是一个完整的对象 型强>
(C2011,6.2.5/20;强调补充)
如果没有限定条件,那么所有指针类型都是完整的类型,甚至是引用类型本身不完整的指针。 如何特定实现使得这项工作没有被标准解决,但通常,所有指针到结构类型都具有相同的大小和表示(与其引用类型的表示无关) )。
这结果很重要,因为结构类型在其定义的右大括号之前是不完整的,所以如果指向不完整类型的指针本身不完整,那么结构不能包含指向其自身类型的另一个结构的指针,例如通常用于实现链表,树和其他数据结构。
另一方面,
未知内容的结构或联合类型[...] 是一种不完整的类型。对于所有声明,它已完成 通过声明相同的结构或联合标记及其定义来输入类型 以后的内容在同一范围内。
(C2011,6.2.5/22)
这是有道理的,因为如果结构类型不知道它的成员是什么,编译器就无法知道它有多大。然后还有意义
结构或联合不得包含不完整或不完整的成员 函数类型(因此,结构不应包含实例 本身,但可能包含指向其自身实例的指针),除外 具有多个命名成员的结构的最后一个成员 可能有不完整的数组类型[...]。
(C2011,6.7.2.1/3;强调补充)
该异常描述了一个名为&#34;灵活数组成员&#34;的C功能,它带有一些警告和限制。这是一个你可以单独阅读(或询问)的切线问题。
此外,所有上述内容都与C和C ++允许您在声明其成员之前通过其标记引用结构类型的事实一致;也就是说,当它不完整时。这可以单独作为前瞻性声明......
struct foo;
......但是除了纪录片之外,它并不起作用,因为不需要前面声明结构类型。您可以再次考虑链表用法,但这种特性绝不仅限于此类情境。
实际上,一个相对常见的用例是实现不透明类型。在这种情况下,由于各种原因,库生成并使用其不希望公开的实现的数据类型。然而,它可以将适当类型的指针分发给客户端代码,并期望接收这样的指针。如果它从不提供引用类型的定义,那么客户端代码必须将引用的对象视为不透明的blob。
答案 3 :(得分:3)
其他人回答“因为编译器知道指针有多大”。但这真的不能回答为什么。有些语言允许在其他类型中使用不完整的类型,并且它们可以正常工作。
如果更改了这些假设中的任何一个,C / C ++将支持结构中不完整的结构:
C / C ++实际上存储了值。许多语言在给定复合数据类型(类或结构)时,存储引用或指针而不是该复合数据类型的实际值
C / C ++想知道完整类型有多大。它希望能够创建数组,计算它们的大小,计算元素之间的偏移量。
C / C ++需要单通道编译。如果编译器愿意注意那里有一个不完整的类型,继续编译直到它发现以后它有多大,然后返回并将类型的大小插入到生成的代码中,不完整类型没问题。
C / C ++希望在定义类型后完成类型。您可以轻松插入一条规则,指出只有abc
的定义可见后nonexistnow
才会完成。相反,C ++希望abc
在结束}
后立即完成,可能是为了简单起见。
最后,指向结构的指针满足所有这些要求,因为C ++标准要求它们这样做。这似乎是一个警察,但确实如此:
在某些平台上,指针的大小随着指向的东西的特征而变化(特别是,在本机指针对四字的地址较大的平台上指向单字节字符的指针)。 C / C ++允许这样做,但要求void*
对于最大的指针足够大,并且指向结构的指针具有固定的大小。这会伤害这样的平台,但他们愿意这样做,以便允许指向完整结构中不完整的结构。
编译器很可能而不是struct small { char c; }
大小为1个字节,因此指向它的指针是“宽”的;但是因为所有指向结构的指针必须具有相同的大小,并且他们不想为每个结构使用宽指针,而是在这样的系统上使用sizeof(small) == 4
。
计算所有指针的大小都不相同,并且结构必须知道它们有多大,这不是一个规律。这些都是c++和c的法律,这些法律选择原因。
但是一旦你有这些理由,你就不得不得出结论struct
的成员必须具有已知的大小(和对齐),而不完整的结构则不然。同时,指向不完整结构的指针。所以一个是允许的,另一个是不允许的。