【问题标题】:Why doesn't this C++ code give compilation error?为什么这个 C++ 代码没有给出编译错误?
【发布时间】:2015-10-30 18:42:09
【问题描述】:
#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 时,它会按预期给出编译错误。

【问题讨论】:

标签: c++ pointers struct structure


【解决方案1】:

此声明

struct abc *d;

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

在这个声明中,不需要有 struct abc 的确切定义,因为没有使用该结构的数据成员。

这个说明符struct abc被称为详细类型说明符。

它在给定范围内引入一个新类型或引用一个已经声明的类型。

【讨论】:

    【解决方案2】:

    你是对的,通常,你需要这种类型的前向声明:

    // 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]: [ 注意: 类名也可以由详细类型说明符隐式声明(7.1.6.3) . ——尾注]

    [C++11: 3.3.2/6]: elaborated-type-specifier中首先声明的类的声明点如下

    • 表格声明

         类键属性说明符序列opt标识符 ;

      标识符在包含声明的范围内被声明为一个类名,否则

    • 对于详细类型说明符的形式

         类键标识符

      如果 elaborated-type-specifier 用于定义的函数的 decl-specifier-seqparameter-declaration-clause在命名空间范围内,identifier 在包含声明的命名空间中被声明为 class-name;否则,除了作为友元声明外,标识符在包含该声明的最小命名空间或块范围内声明[注意:这些规则也适用于模板。 —尾注] [注:其他形式的elaborated-type-specifier不声明新名称, 因此必须引用现有的 type-name。见 3.4.4 和 7.1.6.3。 ——尾注]

    [C++11: 3.4.4/2]: [..] 如果 elaborated-type-specifier 由类键引入,并且此查找未找到先前声明的type-name,或者如果 elaborated-type-specifier 出现在具有以下形式的声明中:

       类键属性说明符序列opt标识符 ;

    elaborated-type-specifier 是一个引入 class-name 的声明,如 3.3.2 中所述。

    【讨论】:

      【解决方案3】:

      它可以工作,因为编译器拥有它需要的所有信息:

      • d 的大小(= 指针大小)1,以及
      • abc 是什么(struct)。

      如果您存储了一个struct abc 类型的对象

      struct abc d;
      

      这将导致错误,因为缺少有关d 的大小和内存布局的信息(因为尚未定义struct abc)。

      如果您遗漏了 struct 关键字:

      abc *d;
      

      那么关于abc 的信息将会丢失,这也是一个错误。


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

      它没有使用,它只是声明。通过指针使用abc 需要先取消引用指针(如果此时未定义abc,则会出错)。


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

      【讨论】:

        【解决方案4】:

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

        这是前向声明-

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

        以下形式的声明

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

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

        struct a 被声明时,它还不知道struct abc 的规格,但你可以转发引用它。

        【讨论】:

        • 你没有解释如何这是一个前向声明。这是OP不理解的微妙问题,这是问题的基础。
        • @LightnessRacesinOrbit 好吧,现在看看。
        • 我已经这样做了。“声明指向结构的指针”不对这种行为负责。将类型命名为 struct abc 而不仅仅是 abc 是。这已在其他答案中得到很好的介绍。
        • @LightnessRacesinOrbit 现在解决问题了吗?
        【解决方案5】:

        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
        } 
        

        【讨论】:

        • 不是这个原因。
        • 原因是,它没有理由显示错误,因为没有错误
        • OP 已经说明了这一点。你应该解释为什么没有错误。
        • 没有错误,因为代码完全符合语言规则,编译器正常工作。我认为这里没有讨论的意义。可以解释的是语言标准背后的基本原理。不需要对结构进行枯燥的定义,因为“仅”是指针的成员不需要。
        • 你需要解释一下,为什么struct a { int e; abc* d; }; 不能编译,尽管它符合你提出的(幻想)规则集。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-04-20
        • 1970-01-01
        • 2023-03-27
        • 1970-01-01
        • 2011-12-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多