C / C ++结构不完整的成员类型不一致?

时间:2018-01-30 13:46:43

标签: c++ c pointers struct

让我们说我们在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)。这对几乎没有任何意义,但对和行为有所不同:

               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; |  × |  ×    |  ×  |   √    | × |  √  |

4 个答案:

答案 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 ++将支持结构中不完整的结构:

  1. C / C ++实际上存储了值。许多语言在给定复合数据类型(类或结构)时,存储引用或指针而不是该复合数据类型的实际值

  2. C / C ++想知道完整类型有多大。它希望能够创建数组,计算它们的大小,计算元素之间的偏移量。

  3. C / C ++需要单通道编译。如果编译器愿意注意那里有一个不完整的类型,继续编译直到它发现以后它有多大,然后返回并将类型的大小插入到生成的代码中,不完整类型没问题。

  4. C / C ++希望在定义类型后完成类型。您可以轻松插入一条规则,指出只有abc的定义可见后nonexistnow才会完成。相反,C ++希望abc在结束}后立即完成,可能是为了简单起见。

  5. 最后,指向结构的指针满足所有这些要求,因为C ++标准要求它们这样做。这似乎是一个警察,但确实如此:

    在某些平台上,指针的大小随着指向的东西的特征而变化(特别是,在本机指针对四字的地址较大的平台上指向单字节字符的指针)。 C / C ++允许这样做,但要求void*对于最大的指针足够大,并且指向结构的指针具有固定的大小。这会伤害这样的平台,但他们愿意这样做,以便允许指向完整结构中不完整的结构。

    编译器很可能而不是struct small { char c; }大小为1个字节,因此指向它的指针是“宽”的;但是因为所有指向结构的指针必须具有相同的大小,并且他们不想为每个结构使用宽指针,而是在这样的系统上使用sizeof(small) == 4

    计算所有指针的大小都不相同,并且结构必须知道它们有多大,这不是一个规律。这些都是的法律,这些法律选择原因。

    但是一旦你有这些理由,你就不得不得出结论struct的成员必须具有已知的大小(和对齐),而不完整的结构则不然。同时,指向不完整结构的指针。所以一个是允许的,另一个是不允许的。