【问题标题】:C/C++ structure incomplete member type inconsistency?C/C++结构不完整成员类型不一致?
【发布时间】:2018-07-09 10:22:13
【问题描述】:

假设我们在 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

更新:

我从下面的答案和 cmet 中总结了这张表,希望它们是正确的并且对阐述有所帮助。

正如@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; |  × |  ×    |  ×  |   √    | × |  √  |

【问题讨论】:

  • 我不同意这个副本。这个问题是关于声明一个不完整类型的对象的实例。这与“为什么我们使用指向不完整类型的指针”不是同一个问题。
  • 我也很困惑为什么这被否决了。第一次看到这似乎是非常不寻常的行为。
  • @Lundin - 什么?它询问为什么指针起作用。另一个问我们为什么使用指针以及为什么非指针不起作用。
  • @Lundin:是的,没有任何特定于 C++ 的内容。另一方面,它也不是 C 特定的。
  • “它使用struct nonexsitnow不,它没有

标签: c c++ c++ c pointers struct


【解决方案1】:

为什么第二个结构体定义没有问题?

因为它是一个指针。即使指针指向的类型不完整,编译器也知道指针的大小。

【讨论】:

  • 它是指向类类型的指针这一事实也很重要。其他指针类型可以有不同的表示,尽管只有联合也会产生指向不完整类型的指针。嗯,MSVC 在成员函数/成员数据指针方面也有问题,但这是长期存在的不符合点。
  • 这显然是一个很好的答案,尽管@John Bollinger 提供了我感兴趣的信息和细节。:)
【解决方案2】:

编译器需要结构/类的大小才能知道在实例化此类类型时必须分配多少内存。

在给定的平台上,sizeof(T*) 将始终返回相同的值,对于任何类型 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&lt;&gt; 如果编译器无权访问类型转换中涉及的类型定义(这是执行运行时检查所必需的),则将失败。示例:Ideone

【讨论】:

  • 每个人似乎都错过了一个重要的点:只写struct Foo *f; 将隐式转发声明Foo。这仅在使用 struct 等时有效,是从 C 继承的行为,并且似乎是 OP 的一个混淆点。
  • 你错了。指向不同类型的指针,尤其是函数指针,可能会有所不同。尽管所有结构/类指针必须具有相同的表示形式。
  • 您说“在给定的平台上,sizeof(T*) 将始终返回相同的值,无论 T 是什么。”这显然是错误的。如果你把自己限制在指向类类型的指针上,你会没事的。
  • @ArneVogel "只写struct Foo *f; 将隐式转发声明Foo",我只是认为它的意思是“好的,你想要一个指向一个结构。”,实际上是“你声明了一个 struct Foo 并想要一个指向它的指针”。对吗?
  • @AlanDawkins 确实如此,here is a demo
【解决方案3】:

首先,您需要了解类型“不完整”意味着什么。 C 是这样定义的:

在翻译单元内的不同点,对象类型可能是 不完整(缺乏足够的信息来确定 该类型的对象)或完整(具有足够的信息)。

(C2011, 6.2.5/1)

请注意,类型完整性是声明的范围和可见性的函数,而不是类型的固有特征。一个类型可以在翻译单元中的某一点不完整,而在另一点完整。

然而,

指针类型可以派生自函数类型或对象类型, 称为引用类型。 [...] 指针类型是一个完整的对象 输入

(C2011,6.2.5/20;已添加重点)

那么,如果没有限定,所有指针类型都是完整类型,即使是其引用类型本身不完整的指针。 特定实现如何 标准没有解决这个问题,但通常,所有指向结构的指针类型都具有相同的大小和表示形式(这与其引用类型的表示形式无关)。

事实证明这很重要,因为结构类型在其定义的右大括号之前是不完整的,所以如果指向不完整类型的指针本身不完整,那么一个结构不能包含指向其自身类型的另一个结构的指针,例如常用于实现链表、树和其他数据结构。

另一方面,

未知内容的结构或联合类型 [...] 是不完全类型。它已完成,对于所有声明 类型,通过声明与其定义相同的结构或联合标记 稍后在同一范围内的内容。

(C2011, 6.2.5/22)

这是有道理的,因为如果编译器不知道其成员是什么,它就无法知道结构类型有多大。那么,

结构或联合不得包含不完整或 函数类型(因此,结构不应包含 本身,但可能包含指向自身实例的指针),除了 具有多个命名成员的结构的最后一个成员 可能有不完整的数组类型 [...]。

(C2011,6.7.2.1/3;已添加重点)

该例外描述了一个称为“灵活数组成员”的 C 功能,它带有一些警告和限制。这是一个无关紧要的问题,您可以单独阅读(或询问)。

此外,上述所有内容都与 C 和 C++ 允许您在声明其成员之前通过其标记引用结构类型这一事实相一致;也就是说,当它不完整时。这可以作为前向声明单独完成...

struct foo;

...但这仅用于文档目的,因为不需要结构类型的前向声明。您可以再考虑一下链表的用法,但此特性绝不限于此类上下文。

确实,一个相对常见的用例是实现不透明类型。在这种情况下,出于各种原因,库会生成并使用不想公开其实现的数据类型。尽管如此,它仍可以将指向此类结构实例的适当类型的指针分发给客户端代码,并期望接收回此类指针。如果它从不提供引用类型的定义,则客户端代码必须将引用的对象视为不透明的 blob。

【讨论】:

  • "如果没有限定,那么,所有指针类型都是完整类型,即使是引用类型本身不完整的指针。" 但是当nosuchtype *p;, " unknown type name 'nosuchtype'" for gcc 和 "use of undeclared identifier 'nosuchtype'" for clang.
  • 当至少声明了引用的类型时,也许指针类型是完整的对象类型
  • @AlanDawkins,nosuchtype * 是否指定一个 complete 类型的问题只有在它指定一个类型的地方才有意义,这正是nosuchtype本身指定一个类型。这是一个单独的问题,但不,它不一定需要单独的声明。特别是,如果nosuchtype 是通过struct 关键字和标签指定的结构类型,则不需要单独声明:struct anything
  • “没有资格,” - 没有双关语。
【解决方案4】:

简短的回答是,因为 标准都这么说。


其他人回答“因为编译器知道指针有多大”。但这并不能回答为什么。有些语言允许在其他类型中包含不完整类型,并且它们工作正常。

如果这些假设发生变化,C/C++ 将支持结构中的不完整结构:

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

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

  3. C/C++ 需要单遍编译。如果编译器愿意注意到那里有一个不完整的类型,则继续编译直到它稍后发现它有多大,然后回来将类型的大小插入到生成的代码中,不完整类型会很好。

  4. C/C++ 希望类型在你定义之后是完整的。您可以轻松地插入一条规则,说明 abc 仅在 nonexistnow 的定义可见时才完成。相反,C++ 希望 abc 在关闭 } 之后立即完成,这可能是为了简单起见。

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

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

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

所有指针的大小都不是计算法则,结构必须知道它们有多大也不是法则。这些都是 的法律,选择这些法律是有原因的。

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

【讨论】:

  • @Deduplicator 我的意思是,在某些硬件平台上,指向字节的指针必须大于指向整数的指针。在这样的平台上,即使结构为 1 字节,指向结构的指针也被强制为整数大小的指针,并且具有 char 成员的结构必须浪费空间,或者指向结构的指针都必须具有 1 字节的分辨率;这些都是“变通办法”。
  • “简短的回答是,因为 c 和 c++ 标准都这么说。”那不是简短的答案,简短的答案是评分最高的答案,“因为它是一个指针。”你设法不那么简洁,少说,少回答,尽管是“简短的答案”,但仍然比最佳答案长。
  • @snb 我的简短回答是一句话。它很短。我将添加一行。这有帮助吗?
  • 是的,但是比“因为它是一个指针”要长很多,而且你的也只回答了技术性问题(是的,从技术上讲,它是因为“c 和 c++ 标准是这样说的”,但是从什么时候开始是“因为他们这么说”是对为什么的充分回答)。鉴于前两个答案涵盖了这么多,我看不出这个答案有什么价值。这些答案变得更具可读性,减少了无意义的阐述和冗长的句子。
  • @snb “因为编译器知道指针的大小”并没有说明为什么。您不妨说“因为该类型中有一个*”。编译器需要知道大小的原因是因为我们想要单遍编译以及在声明后立即为实例创建存储的能力,而 C/C++ 实际上存储结构/类类型的值。更改其中的 any,C/C++ 将允许其他结构中的不完整结构。停留在“我们知道指针有多大”并不比“标准这么说”更深刻。
猜你喜欢
  • 2022-01-09
  • 1970-01-01
  • 2016-05-08
  • 2016-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多