12

在回答警告:从不兼容的指针类型为链接列表数组赋值时,我注意到任何带有struct关键字的未声明标识符都被视为前向声明的标识符。

例如下面的程序编译得很好:

/* Compile with "gcc -std=c99 -W -Wall -O2 -pedantic %" */
#include <stdio.h>

struct foo 
{
    struct bar *next;  /* Linked list */
};


int main(void) {
    struct bar *a = 0;
    struct baz *b = 0;
    struct foo c = {0};

    printf("bar -> %p\n", (void *)a);
    printf("baz -> %p\n", (void *)b);
    printf("foo -> %p, %zu\n", (void *)&c, sizeof c); /* Remove %zu if compiling with -ansi flag */
    return 0;
}

我的问题:哪个规则指导C编译器将 undeclared struct identifiers 视为前向声明的不完整struct类型?

4

4 回答 4

10

标准说(6.2.5.28

所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。

这意味着编译器知道如何表示指向任何结构的指针,即使是那些(还)未定义的结构。
您的程序只处理指向此类结构的指针,所以没关系。

于 2015-06-11T09:14:31.100 回答
7

它在 6.2.5 类型和 6.7.2.3 标记中进行了描述。

struct identifier是一个对象类型。

6.2.5 类型

  1. 存储在对象中或由函数返回的值的含义取决于用于访问它的表达式的类型。(声明为对象的标识符是最简单的此类表达式;类型在标识符的声明中指定。)类型分为对象类型(描述对象的类型)和函数类型(描述函数的类型)。在翻译单元内的不同点,对象类型可能是不完整的(缺乏足够的信息来确定该类型对象的大小)或完整的(具有足够的信息)。37)

37) 一个类型在整个翻译单元中可能是不完整的或完整的,或者它可能在翻译单元内的不同点改变状态。

  1. 未知大小的数组类型是不完整的类型。对于该类型的标识符,它是通过在稍后的声明中指定大小(具有内部或外部链接)来完成的。 未知内容的结构或联合类型(如 6.7.2.3 中所述)是不完整类型。对于该类型的所有声明,通过稍后在相同范围内声明相同的结构或联合标记及其定义内容来完成。

6.7.2.3 标签

  1. 具有相同范围并使用相同标记的结构、联合或枚举类型的所有声明都声明相同的类型。不管在同一翻译单元中是否存在标记或该类型的其他声明,该类型是不完整的 129) 直到紧接在定义内容的列表的右大括号之后,然后才完成。

129) 不完整类型只能在不需要该类型对象的大小时使用。例如,当 typedef 名称被声明为结构或联合的说明符时,或者在声明指向或返回结构或联合的函数时,不需要它。(参见 6.2.5 中的不完整类型。)在调用或定义这样的函数之前,规范必须是完整的。

于 2015-06-09T07:51:18.903 回答
6

除了 2501 提供的答案以及您对它的评论“在我的情况下,甚至没有前向声明”之外,以下内容。

struct tag如果之前没有声明,任何使用 a 都算作结构类型的(前向)声明。虽然更正式的方式是说这只是一种类型,但由于 C 标准没有提到“结构类型的前向声明”,只是完整和不完整的结构类型 (6.2.5p22)

6.7.2 类型说明符告诉我们struct-or-union-specifier是一个类型说明符,而6.7.2.1 结构和联合说明符第 1 段告诉我们反过来struct identifier是一个struct-or-union-specifier

假设你有一个链表声明,比如

struct node {
    struct node *next;
    int element;
};

那么这个不完整类型的“隐式前向声明”对于这个结构的工作是必不可少的。毕竟,类型struct node 在终止分号处是完整的。但是您需要引用它才能声明next指针。

此外,struct node声明(类型不完整)可以超出范围,就像任何其他声明一样。例如,如果您有一些原型,就会发生这种情况

int function(struct unknown *parameter);

struct unknown声明结束时立即超出范围。任何进一步声明struct unknown的 s 都与这个不同。这在6.2.5p22的文本中暗示:

未知内容的结构或联合类型(如 6.7.2.3 中所述)是不完整类型。对于该类型的所有声明,它是通过在相同范围内声明相同的结构或联合标记及其定义内容来完成的。

这就是 gcc 对此发出警告的原因:

foo.c:1:21: warning: 'struct unknown' declared inside parameter list
foo.c:1:21: warning: its scope is only this definition or declaration, which is probably not what you want

您可以通过在其前面添加一个额外的前向声明来解决此问题,这使得范围更早开始(因此更晚结束):

struct unknown;
int function(struct unknown *parameter);
于 2015-06-16T21:52:32.463 回答
2

我认为使用不完整结构类型的最优雅的用例是这样的:

struct foo 
{
    struct bar *left;
    struct bar *right;
};
struct bar
{
    int something;
    struct foo *next;
};

即双重递归,其中a指向b,b指向a。这种情况可能是这个特性被包含在原始 C 语言规范中的一个原因。

最初的问题是是否所有结构标识符都自动向前声明。我认为最好说所有不完整的结构定义都被自动视为前向声明

编辑:在关于文档的评论之后,让我们看看 C 语言圣经:Kerninghan&Ritchie - The C Programming Language,“6.5 自引用结构”部分说:

有时,需要一种自引用结构的变体:两个相互引用的结构。处理这种情况的方法是:

struct t {
    ...
    struct s *p;   /* p points to an s */
};
struct s {
    ...
    struct t *q;   /* q points to a t */
};

我同意,有可能实现另一种方式,但我认为这是 C 语言作者的良好动机,我同意他们的观点,即这是实现这一点的优雅方式。

于 2015-06-17T09:14:08.007 回答