为什么这段C++代码没有编译错误?

4
#include<iostream>
using namespace std;

struct a{
    int e;
    struct abc *d;
};

struct abc{
    int c;
};

int main()
{
 return 0;  
}

在定义struct a之后,我定义了struct abc,并声明了一个指向abc结构的指针。这应该会导致编译错误,因为在声明之前使用了abc。但是,实际上没有出现错误,为什么呢?

然而,当我将其替换为struct abc d而不是struct abc *d时,它按预期产生编译错误。


拥有指向未知数据类型的指针不需要额外的信息 - 指针就是指针。另一方面,拥有未知数据类型的实例无法解决,因为您需要知道结构的大小以分配空间。 - enhzflep
研究不足的问题。 - vish4071
@BoPersson 但是有些架构 sizeof(char*) != sizeof(int*) 指针的大小是否可能在数据指针和函数指针之间有所不同? 是否有任何平台,不同类型的指针具有不同的大小? 在这些情况下,编译器如何知道指针的大小? - phuclv
@LưuVĩnhPhúc - 是的,但在给定系统上,指向结构体和类的指针需要具有相同的大小。 - Bo Persson
@BoPerssonпјҡж•ҙдёӘй—®йўҳзҡ„зӣ®зҡ„жҳҜвҖңзј–иҜ‘еҷЁеҰӮдҪ•зҹҘйҒ“abcжҳҜдёҖдёӘзұ»вҖқпјҲC++жІЎжңүз»“жһ„дҪ“пјүгҖӮ - Lightness Races in Orbit
显示剩余2条评论
5个回答

6
这个声明
struct abc *d;

一方面声明了struct abc,另一方面声明了类型为struct abc *的指针d

在这个声明中,没有必要精确定义struct abc,因为结构体的任何数据成员都没有被使用。

这个限定符struct abc被称为精细类型限定符。

它在给定的作用域中引入了一个新类型,或者引用了已经声明的类型。


5

你说得对,通常情况下,你需要这种类型的前向声明:

// Forward declaration
struct bar;

struct foo
{
    bar* d;   // only a pointer-to-bar; forward declaration sufficient
};

struct bar
{
    int c;
};

然而,你(由于某种原因)使用了过时的习惯,在类型名称之前写上了struct。(在C语言中需要这样做,但在C++中从未需要过。)

struct foo
{
    struct bar* d;
};

struct bar
{
    int c;
};

因为你在那里写了struct bar而不是只写bar这本身就算是一种前置声明。编译器现在知道bar是一个类型,这就是它需要知道的全部。
这有点难以理解和微妙,但这就是为什么你不再需要先前的前置声明。
[C++11: 3.1/4]: 注意:通过 elaborated-type-specifier(7.1.6.3)也可以隐式声明类名。 [C++11: 3.3.2/6]: 对于在 elaborated-type-specifier 中首次声明的类,其声明点如下: - 对于形式为 class-key attribute-specifier-seqopt identifier ; 的声明,identifier 被声明为 class-name 并处于包含该声明的作用域中; - 对于 elaborated-type-specifier 形式,若其出现在函数的 decl-specifier-seq 或 parameter-declaration-clause 中并且函数定义于命名空间作用域中,则 identifier 在该命名空间中被声明为 class-name;否则(除了 friend 声明),identifier 被声明为 class-name 并处于包含该声明的最小命名空间或块作用域中。这些规则同样适用于模板中。其他形式的 elaborated-type-specifier 不会声明新名称,因此必须引用现有的 type-name。详见 3.4.4 和 7.1.6.3。 [C++11: 3.4.4/2]: 若 elaborated-type-specifier 是由 class-key 引入的,并且该查找未找到先前声明的 type-name,或者 elaborated-type-specifier 出现在具有以下形式的声明中:class-key attribute-specifier-seqopt identifier;,则 elaborated-type-specifier 是一个声明,它按照 3.3.2 中描述的引入 class-name。

2

这段代码能够正常运行,因为编译器拥有所有必要的信息:

  • d 的大小(等于指针的大小)1
  • abc 是什么类型(是一个结构体)

如果你存储了一个 结构体 abc 类型的对象:

struct abc d;

如果没有定义struct abc,那么翻译会出现错误,因为缺少关于d大小和内存布局的信息。

另外,如果省略了struct关键字:

abc *d;

如果缺少关于abc的信息,那么它也将是一个错误。


这应该会抛出编译错误,因为在其声明之前使用了abc

它并没有被使用,只是声明。通过指针使用abc需要先对指针进行解引用(如果此时abc未定义,则会出现错误)。


1 允许使用不完整类型的指针,因为指针的大小和内存布局不取决于它所指向的内容。


0

允许声明不完整的指针类型。

这是前向声明 -

前向声明是在实际定义之前的声明,通常是为了在定义不可用时能够引用已声明的类型。当然,并非所有事情都可以使用已声明但未定义的结构,但在某些上下文中可以使用。这种类型称为不完整类型。

以下形式的声明

  class-key attr identifier ;   // struct abc *d; (Your case)

声明一个类类型,该类型将在此范围内稍后定义。在定义出现之前,此类名称具有不完整的类型。

当声明struct a时,它还不知道struct abc的规格,但您可以进行前向引用。


你没有解释这是如何进行前向声明的。这是一个微妙的问题,OP不理解,这也是问题的基础所在。 - Lightness Races in Orbit
@LightnessRacesinOrbit 好的,现在看到了。 - ameyCU
1
我已经完成了。"声明指向结构体的指针"并不负责这种行为。将类型命名为struct abc而不仅仅是abc是原因。其他答案已经详细介绍了这一点。 - Lightness Races in Orbit
@LightnessRacesinOrbit 现在解决问题了吗? - ameyCU

-2
struct a中,您引用了指向struct abc指针,因此编译器不需要更多关于它的信息来计算d成员的大小。
在后续阶段,如果需要(例如对其进行解引用),它将检查struct abc是否已定义。这解释了标准的基本原理:
  • 禁止声明未知类型的变量(当未转发时,abc是这样一个未知类型)

  • 允许将它们声明为指向不完全类型的指针。(struct abc是不完全类型:至少已知它是某个struct

实际上,将结构声明为
 struct a {
    struct abc *d;
 }

这相当于转发 (struct) 类型 abc 的声明,如下所示

struct abc;           // forward
struct a {
   abc *d;            // legal
};

然而类型abc是不完整的,因此以下是非法的

struct z {
   struct abc y;   // error : incomplete struct type
}

出于好奇,这是可以的:

struct A {
   struct B* ptr1;    // forwards declaration of B, and use it
   B* ptr2;           // B is a known (incomplete-)type name now
} 

1
这不是原因。 - Lightness Races in Orbit
原因是,它没有显示错误的理由,因为没有错误。 - Michel Billaud
1
你需要解释一下,为什么 struct a { int e; abc* d; }; 虽然符合你提出的(幻想的)规则集,但却无法编译。 - IInspectable
你对“为什么没有报错?”的回答是“因为没有错误”?哇,真有帮助。 - Lightness Races in Orbit
不需要对结构体进行详细定义,因为对于一个“仅仅”是指针的成员而言,这并不是必须的。但是,前向声明是必要的,而且OP正在询问为什么在这种情况下它(表面上)没有被使用。 - Lightness Races in Orbit
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接