所有结构标识符都自动向前声明
在回答警告:从链表数组的不兼容指针类型进行赋值时 ,我注意到任何用struct
关键字识别的未声明标识符都被视为前向声明的标识符。
例如, 下面的程序编译得很好:
/* Compile with "gcc -std=c99 -W -Wall -O2 -pedantic %" */ #include 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
编译器将未声明的struct identifier
视为前向声明的不完整struct
类型?
标准说( 6.2.5.28 )
所有指向结构类型的指针都应具有相同的表示和对齐要求。
这意味着编译器知道如何表示任何结构的指针,甚至是那些尚未定义的结构。
你的程序只处理指向这种结构的指针 ,所以没关系。
它在6.2.5类型和6.7.2.3标签中描述。
struct identifier
是一种对象类型。
6.2.5类型
- 存储在对象中或由函数返回的值的含义由用于访问它的表达式的类型确定。 (声明为对象的标识符是最简单的表达式;类型在标识符的声明中指定。)类型被分区为对象类型(描述对象的类型)和函数类型(描述函数的类型)。 在翻译单元内的各个点处,对象类型可能是不完整的(缺少足够的信息来确定该类型的对象的大小)或完整(具有足够的信息)。 37)
37)在整个翻译单元中类型可能是不完整或完整的,或者它可能在翻译单元内的不同点处改变状态。
- 未知大小的数组类型是不完整类型。 对于该类型的标识符,通过在稍后的声明中指定大小(具有内部或外部链接)来完成。 未知内容的结构或联合类型(如6.7.2.3中所述)是不完整类型。 对于该类型的所有声明,通过在稍后的同一范围内声明相同的结构或union标记及其定义内容,它已完成。
6.7.2.3标签
- 具有相同作用域并使用相同标记的结构,联合或枚举类型的所有声明都声明相同的类型。 无论是否存在标签或该类型的其他声明在同一翻译单元中,该类型都是不完整的129)直到定义内容的列表的右括号之后,并且此后完成。
129)仅当不需要该类型的对象的大小时,才可以使用不完整类型。 例如,当typedef名称声明为结构或联合的说明符,或者声明返回结构或联合的函数或函数时,不需要它。 (参见6.2.5中的不完整类型。)在调用或定义此类函数之前,必须完成规范。
除了2501提供的答案,以及您对它的评论“ 在我的情况下,甚至没有前瞻性声明 ”,以下内容。
任何 struct tag
使用都算作结构类型的(前向)声明,如果之前没有声明的话。 虽然更正式的方式是说这只是一种类型,因为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中所述)是不完整类型。 对于该类型的所有声明,通过在稍后的同一范围内声明相同的结构或union标记及其定义内容,它已完成。
这就是为什么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);
我认为使用不完整结构类型的最优雅的用例是这样的:
struct foo { struct bar *left; struct bar *right; }; struct bar { int something; struct foo *next; };
即双递归,其中b和b指向a。 此类情况可能是此function包含在原始C语言规范中的原因。
原始问题是是否所有结构标识符都是自动转发声明的 。 我认为最好说所有不完整的结构定义都被自动视为前向声明 。
编辑:在关于文档的评论之后,让我们看看C语言圣经:Kerninghan&Ritchie – C编程语言,“6.5自引用结构”部分说:
偶尔,人们需要一种自引用结构的变体:两种相互引用的结构。 处理这个问题的方法是:
struct t { ... struct s *p; /* p points to an s */ }; struct s { ... struct t *q; /* q points to at */ };
我同意,有可能实现另一种方式,但我认为这是C语言作者的良好动机,我同意他们这是实现这一点的优雅方式。