我想要一个标准参考,解释为什么以下代码在C语言中会触发一条符合性警告(使用gcc -pedantic
测试; "typedef重定义"),但在C++中则是可以的(使用g++ -pedantic
):
typedef struct Foo Foo;
typedef struct Foo Foo;
int main() { return 0; }
为什么在C语言中不能重复定义typedef
?
(这对于一个C项目的头文件结构有实际的影响.)
我想要一个标准参考,解释为什么以下代码在C语言中会触发一条符合性警告(使用gcc -pedantic
测试; "typedef重定义"),但在C++中则是可以的(使用g++ -pedantic
):
typedef struct Foo Foo;
typedef struct Foo Foo;
int main() { return 0; }
为什么在C语言中不能重复定义typedef
?
(这对于一个C项目的头文件结构有实际的影响.)
因为C++标准明确规定了这样做是可以的。
参考文献:
C++03标准 7.1.3 typedef specifier
§7.1.3.2:
在给定的非类作用域中,typedef声明符可用于重新定义该作用域中声明的任何类型的名称,以使其引用它已引用的类型。
[例如:
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
—end example]
typedef
名称没有链接性,而C99标准禁止具有相同作用域和名称空间内多个声明的标识符没有链接说明符。
参考文献:
C99标准:§6.2.2 链接指示符
§6.2.2/6说明:
以下标识符没有链接性:除对象或函数之外的任何内容声明的标识符;声明为函数参数的标识符;在没有extern存储类说明符的情况下声明的对象的块作用域标识符。
进一步的,§6.7/3说明:
如果标识符没有链接性,在具有相同作用域和名称空间内仅能有一个声明该标识符(在声明符或类型说明符中),除非在6.7.2.3中指定了标记。
2011年的C标准于2011年12月19日星期一由ISO(更确切地说,是它被发布的通知被添加到委员会网站上,并且该标准可能已经在2011年12月08日左右被发布)发布。请参见WG14网站上的公告。不幸的是,来自ISO的PDF的费用为338瑞士法郎,而来自ANSI的费用为387美元。
由于独立预处理器的复杂性,我非常需要“不完整结构声明”。那么你是说如果这些前向声明被完整头文件再次typedef,则我不能typedef它们吗?
多少是这样。我没有真正遇到过这个问题(尽管工作中有些系统非常接近必须担心这个问题),所以这有点暂定,但我相信它应该有效。
通常情况下,头文件以足够的细节描述了“库”(一个或多个源文件)提供的外部服务,以便库的用户能够编译它。特别是在有多个源文件的情况下,可能还会有一个内部头文件,例如定义完整类型的头文件。
所有头文件都是(a)自包含和(b)幂等的。这意味着您可以(a)包含头文件并自动包含所有必需的其他头文件,并且(b)您可以多次包含头文件而不会招致编译器的愤怒。后者通常通过头文件保护实现,尽管有些人更喜欢使用#pragma once
- 但这不可移植。
因此,您可以有一个公共头文件如下:
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
#include <stddef.h> // size_t
typedef struct mine mine;
typedef struct that that;
extern size_t polymath(const mine *x, const that *y, int z);
#endif /* PUBLIC_H_INCLUDED */
到目前为止,还没有太多争议(尽管可以合理地怀疑该库提供的接口非常不完整)。
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include "public.h" // Get forward definitions for mine and that types
struct mine { ... };
struct that { ... };
extern mine *m_constructor(int i);
...
#endif /* PRIVATE_H_INCLUDED */
再次强调,这并不具有争议性。头文件public.h
必须首先列出;这提供了自我包含的自动检查。
需要polymath()
服务的任何代码都会写入:
#include "public.h"
polymath()
服务的任何代码都会写入:#include "private.h"
此后,一切都像正常一样运作。
如果有另一个库(称之为multimath()
)使用polymath()
服务,则该代码可以像任何其他消费者一样包含public.h
。如果polymath()
服务是multimath()
外部接口的一部分,则multimath.h
公共头文件将包括public.h
(抱歉,在这里我换了术语)。如果multimath()
服务完全隐藏了polymath()
服务,则multimath.h
头文件不会包括public.h
,但multimath()
私有头文件可能会这样做,或者需要polymath()
服务的单个源文件在需要时可以包括它。
只要您严格遵循正确地包含头文件的纪律,就不会遇到重复定义的问题。
如果随后发现您的某个标头包含两组定义,其中一组可以无冲突使用,而另一组可能(或总是)与某个新标头(及其中声明的服务)发生冲突,则需要将原始标头拆分为两个子标头。每个子标头都遵循此处详述的规则。原始标头变得微不足道-一个头文件保护和包括两个单独文件的行。所有现有的工作代码保持不变-尽管依赖关系发生了变化(额外的文件要依赖)。现在,新代码可以包括相关的可接受子标头,同时使用与原始标头冲突的新标头。
当然,你可以拥有两个头文件,它们之间是无法调和的。举个人为的例子,如果有一个(设计不良的)头文件声明了一个与<stdio.h>
中版本不同的FILE
结构,那么你就没办法了;代码可以包含设计不良的头文件或<stdio.h>
,但不能同时包含。在这种情况下,应该修改设计不良的头文件,使用一个新名称(也许是File
,但也可能是其他名称)。如果你需要将两个产品的代码合并成一个,在公司收购后遇到一些共同的数据结构,例如数据库连接的DB_Connection
,那么你更有可能遇到这种麻烦。在没有C++namespace
功能的情况下,你只能对其中一个或两个代码进行重命名操作。friend
和 constexpr
,它们也是特殊类型的声明。C++ 将 typedef
从 storage-class-specifier 移动到 decl-specifier 中。这种差异并不能证明什么,只是一种不同的定义语法方式,但它表明 C++ 认为“让我们拥有几种不同类型的声明”,而 C 则认为“让我们尽可能简单地容纳 typedef”。 - Steve Jessopstruct foo { int a; int b; };
foo f;
struct foo { int a; int b; };
typedef struct foo foo;
foo f;
在C语言规范中没有说明为什么这是无效的。规范不是澄清这一点的正确位置。顺便说一下,在C1x中允许这样做(根据我上一个问题的答案)。
我认为,这个C1x特性支持将宏转换为typedefs(如果宏完全相同,则前者允许重复)。
int x; int x;
在 C 语言中是合法的(在文件作用域),它只是两个具有相当接近的 外部链接 的 对象 的试探性定义。 - CB Bailey
struct Foo
,但这与代码风格不符。 (这与“实例化”此hashtable“模板”有关)。 - Kerrek SB请使用 typedef struct Foo Foo;
,以平息编译器。 - R. Martinho Fernandes