为什么包含指向相同类型的指针的 "typedef struct { struct S *s; } S;" 能够编译通过?

22

我试图使用 typedef 定义一个包含指向同一类型的另一个结构体指针的结构体。

这是我认为最好的版本:

typedef struct Element
{
    char value;
    struct Element *next;
} Element;

为什么这个变量也可以编译和执行?

typedef struct
{
    char value;
    struct Element *next;
} Element;

第一个可以这么描述:"名字为 struct Element,现在叫做 Element"。第二个可以描述为:"取这个匿名的 struct 并且把它叫做 Element"

但是为什么我还能在第二种情况下声明一个 struct Element (在结构体内部)呢?

(在 GCCMSVC 中使用)


1
在第二种情况下,你的 next 实际上变成了一个结构体 struct{},你需要深入了解一下 typedef 的实际作用。 - bigkm
@bigkm 把 struct Element *next; 替换成 Element *next; 被报告为错误。你是什么意思? - ordag
1
@bigkm:struct struct {} 是什么意思?在第二种情况下,next 是指向 struct Element 的指针;问题在于 struct Element 是不完整的类型。 - Keith Thompson
如何进行版本控制:https://dev59.com/FW865IYBdhLWcg3wFqrY - Ciro Santilli OurBigBook.com
3个回答

23
在第一种情况下,你的结构体有两个等效的名称:struct Element (其中Element是结构体标签)和Element(其中Element是一个typedef,是现有类型的别名)。
在第二种情况下,你只是没有为结构体定义一个标签。通常这是完全有效的,但在这里你在next成员的声明中引用了不存在的类型struct Element
在这种情况下,struct Element是一个不完整的类型。你不能声明不完整类型的对象,但可以声明指向它们的指针。
声明为:
typedef struct
{
    char value;
    struct Element *next;
} Element;

该声明合法,但它并不会使得next成为指向封闭类型的指针。它是一个指向某个未完成类型的指针,除非你声明完整的类型,否则你将无法引用它。

你的第二个声明是众多看似毫无意义但仍然合法的C语言语句之一。

你也许可以考虑省略typedef,并始终将该类型称为struct Element。很多人喜欢为结构类型使用一个单词名称,但我的个人观点是这没有太多好处(除非该类型是真正的不透明类型,即类型的用户甚至不知道它是一个结构体)。这是一种风格问题。

请注意,在定义内部需要用struct Element而不是Element来引用该类型,因为typedef名称Element还不可见。

结构体标签和typedef具有相同的名称可能看起来很困惑,但这是完全合法的。结构体标签和typedef位于不同的命名空间中(在C语言中,而不是C++语言);结构体标签只能出现在struct关键字之后。

另一种选择是将typedef与结构体定义分开:

typedef struct Element Element;

struct Element {
    char value;
    Element *next;
};

typedef 中可以使用不完整的类型名。


啊,哇,那很有道理,我不知道关于不完整类型的指针。谢谢你。 - ordag

4
你的第一个选项是正确的。你的第二个选项并不能实现表面上看起来想要做的事情。
在 C 语言中,可以在任何地方前向声明结构体类型,即使在声明其他内容的过程中也可以。这种声明传递的作用域规则非常令人困惑,因此我不打算解释它们——只需要说应该避免这样做。这就是为什么第二个结构不会报错。但它对编译器的意义是这样的:
struct _Anonymous_1 // name not actually accessible to code
{
    char value;
    struct Element *next;
};
typedef struct _Anonymous_1 Element;

在这段代码之后,类型 "struct Element" 与类型 "Element" 完全无关,并且没有被完全声明。如果你尝试去使用这个类型,例如在...
char cadr(Element *cons)
{
    return cons->next->value;
}

编译器会不高兴:

test.c: In function ‘cadr’:
test.c:9:22: error: dereferencing pointer to incomplete type

第二种选择是使用“Element”代替“struct Element”,以便在类型定义内部和任何其他地方都可以使用,如下所示:

typedef struct Element Element;
struct Element
{
    char value;
    Element *next;
};

但是在C语言中,没有办法避免手动确保"struct Element"和"Element"是相同的。如果你不想自己处理这个问题,C++就在那边等着你了 ⟶


谢谢。在那种语言中,你总是会学到一些新的东西,让你感到惊叹。 (你的回答晚了几秒钟,所以我会接受第一个,但这个绝对有助于更好地理解) - ordag
哦,尤其是你的第三个版本,在分号之前加上typedef;-) - ordag
1
提醒一下:在您自己的代码中,不能安全地使用像“_Anonymous_1”这样的名称,因为以下划线开头的标识符是保留给实现的。我知道您在伪代码中使用它来展示编译器内部的情况,所以这不是一个更正,只是一个澄清。 - Keith Thompson
是的,这是故意为之的。(我考虑过加入美元符号,但最终决定这样做会带来更多的困惑而不是帮助。) - zwol

0

为了存储指向结构体的指针,编译器不需要知道其内容或大小,只需要知道指针的大小。

在您的第一个示例中,直到结构定义之后,struct Element才是不完整的类型,但这可以工作,因为您只声明了一个指向它的指针,而不是结构本身的实例。

在您的第二个示例中,您根本没有声明struct Element结构(struct ElementElement不是同一件事)。虽然您仍然可以在结构中包含指针,但它不引用相同的类型,而是引用尚未定义的struct Element。 typedef中的结构定义是匿名结构,因此您只能使用Element(不带struct关键字)来引用它。因此,第二个示例无法按预期工作。


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