重复typedefs – 在C无效,但在C ++有效?

我想要一个标准的引用,为什么下面的代码在C语言中触发了一个符合性警告(使用gcc -pedantic ;“typedef gcc -pedantic ”进行testing),但在C ++( g++ -pedantic )中罚款:

 typedef struct Foo Foo; typedef struct Foo Foo; int main() { return 0; } 

为什么我不能在C中重复定义typedef

(这对C项目的头结构具有实际意义。)

为什么在C ++中编译?

因为C ++标准明确地这样说。

参考:

C ++ 03标准7.1.3 typedef说明符

§7.1.3.2:

在给定的非类范围中,可以使用typedef说明符来重新定义在该范围中声明的任何types的名称,以引用它已经引用的types。

[例:
typedef struct s {/ * … * /} s;
typedef int I;
typedef int I;
typedef二;
– 例子]

为什么这不能在C编译?

typedef名称没有链接,C99标准不允许没有链接规范的标识符具有多个具有相同作用域和相同名称空间的声明。

参考:

C99标准:§6.2.2标识符的关联

§6.2.2/ 6指出:

下面的标识符是没有联系的:一个标识符被声明为对象或函数以外的任何东西; 一个标识符被声明为一个函数参数; 一个块范围标识符,用于在没有存储类speciFireXtern的情况下声明的对象。

另外§6.7/ 3指出:

如果一个标识符没有链接,标识符(在一个声明符或types说明符中)的声明不得超过 6.7.2.3中规定的相同范围和相同的名称空间

标准C现在是ISO / IEC 9989:2011

2011年C标准于2011年12月19日星期一由ISO发布(更准确地说,它已经发布的通知已经在19日join委员会网站;标准可能已经发布为“很久以前”如2011-12-08)。 请参阅WG14网站上的公告。 可悲的是, 来自ISO的PDF成本为338瑞士法郎, ANSI 387美元

  • 您可以从ANSI获得30美元的INCITS / ISO / IEC 9899:2012(C2011)的PDF文件。
  • 您可以从ANSI获得30美元的INCITS / ISO / IEC 14882:2012(C ++ 2011)PDF。

主要答复

问题是“在C中允许重复typedefs”? 答案是“否 – 不符合ISO / IEC 9899:1999或9899:1990标准”。 原因可能是历史的; 原来的C编译器不允许这样做,所以原来的标准化程序(负责标准化已经在C编译器中可用的标准化程序)标准化了这种行为。

请参阅Als的答案 ,C99标准禁止重复typedefs。 C11标准已经将第6.7节中的规则改为:

3如果标识符没有链接,标识符(在声明符或types说明符中)的声明不得超过一个,且具有相同范围和相同名称空间,但下列情况除外:

  • 一个typedef名字可以被重新定义为表示与当前相同的types,只要该types不是一个可变的修饰types;
  • 标签可以按照6.7.2.3的规定重新申报。

所以现在在C11中有一个明确的命令重复typedef。 了解C11兼容C编译器的可用性。


对于那些仍然使用C99或更早版本的用户来说,后续问题大概是这样的:“那么,如何避免遇到重复typedefs问题?

如果遵循这样的规则,即有一个单独的头文件来定义多个源文件中需要的每种types(但是可以有多个头文件来定义这种types;每个单独的types只能在一个头文件中find),if这个头文件在任何时候都需要使用,那么你就不会遇到冲突。

如果只需要指向types的指针而不需要分配实际结构或访问它们的成员(不透明types),那么也可以使用不完整的结构声明。 再次,设置关于哪个头声明不完整types的规则,并在需要types的地方使用该头。

另请参见C中的externvariables是什么 ; 它谈论variables,但types可以有点相似。


来自评论的问题

我非常需要“不完整的结构声明”,因为单独的预处理复杂性,禁止某些内含物。 所以你说我不能在完整的头文件中再次使用typedefed来input那些前向声明?

或多或less。 我没有必要去处理这个问题(虽然有些系统在工作中已经非常接近于担心这个问题),所以这只是一个小小的尝试,但我相信它应该能够工作。

一般来说,一个头文件描述了由一个“库”(一个或多个源文件)提供的外部服务,其详细程度足以让该库的用户能够用它编译。 特别是在有多个源文件的情况下,也可能有一个内部头文件,例如定义了完整的types。

所有标题是(a)独立的和(b)幂等的。 这意味着你可以(a)包含头文件,并自动包含所有必需的其他头文件,(b)可以多次包含头文件,而不会引起编译器的愤怒。 后者通常是通过头文件来实现的,虽然有些人喜欢#pragma once – 但这不是可移植的。

所以,你可以有这样的公共标题:

public.h

 #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 */ 

到目前为止,还没有非常有争议的(虽然人们可以合法地怀疑这个图书馆提供的界面是非常不完整的)。

private.h

 #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" 

此后,一切正常。

其他提供者代码

如果有另一个使用polymath()服务的库(称为multimath() ),那么该代码将包含public.h ,就像任何其他使用者一样。 如果polymath()服务是multimath()的外部接口的一部分,那么multimath.h公共头文件将包含public.h (对不起,我在这里转换了接近结尾的术语)。 如果multimath()服务完全隐藏了polymath()服务,那么multimath.h头文件将不包含public.h ,但multimath()私有头文件可能会这样做,或者需要polymath()的个别源文件polymath()服务可以在需要时包括它。

只要你恪守包括正确标题的规则,那么你就不会陷入双重定义的困境。

如果你后来发现你的一个头文件包含两组定义,一个可以没有冲突地使用,另一个可以(或者总是)与一些新的头文件(以及其中声明的服务)冲突,那么你需要分割原始标题分成两个子标题。 每个子标题单独遵循这里阐述的规则。 原始头文件变得微不足道 – 头文件和包含两个单独文件的行。 所有现有的工作代码保持不变 – 虽然依赖关系发生变化(依赖于额外的文件)。 新代码现在可以包含相关的可接受的子标题,同时也使用与原始标题冲突的新标题。

当然,你可以有两个头,这是不可调和的。 对于一个人为的例子,如果有一个(devise糟糕的)头文件声明不同版本的FILE结构(来自<stdio.h>的版本) 代码可以包含devise错误的头文件或<stdio.h>但不能同时包含两者。 在这种情况下,devise不当的头文件应该修改为使用新名称(也许是File ,但也许是别的)。 如果您必须在企业接pipe之后将两个产品的代码合并为一个代码,并使用一些常见的数据结构(例如用于数据库连接的DB_Connection ,则可能会更现实地遇到此问题。 在没有C ++ namespacefunction的情况下,您会遇到一个或两个批处理代码的重命名练习。

因为7.1.3 / 3和/ 4,你可以用C ++来完成。

你不能在C99中完成它,因为它在6.7.7中没有任何等价的特殊情况,所以重新声明一个typedef名称遵循与重新声明任何其他标识符相同的规则。 具体来说6.2.2 / 6(typedefs没有链接)和6.7 / 3(没有链接的标识符只能在相同范围内声明一次)。

记住typedef是C99中的存储类说明符,而在C ++中则是decl说明符。 不同的语法导致我怀疑C ++作者决定花更多的努力使typedef“一种不同的声明”,所以可能愿意花更多的时间和文字在他们的特殊规则。 除此之外,我不知道C99作者(缺乏)的动机是什么。

[编辑:参见Johannes对C1x的回答。 我并没有遵循这一点,所以我应该停止使用“C”来表示“C99”,因为我可能在批准和发布时不会注意到它。 这已经够糟的了:“C”应该是“C99”,但实际上的意思是“C99,如果你幸运的话,但是如果你必须支持MSVC,那么C89”。]

[再次编辑:实际上,它已经发布,现在是C11。 活泉]

c规范中没有什么说明为什么这是无效的。 规范是错误的地方来澄清这一点。 FWIW允许C1x(根据我收到的答案之一,我的最后一个问题)。

我想这个c1xfunction支持将macros转换为typedefs(前者允许在相同的情况下重复)。

很多人都参照标准回答了,但没有人说,为什么这里的C和C ++标准有所不同。 嗯,我相信,在C ++中允许重复typedefs的原因是,C ++隐式地声明结构和类为types。 所以在C ++中以下是合法的:

 struct foo { int a; int b; }; foo f; 

在C中,必须写:

 struct foo { int a; int b; }; typedef struct foo foo; foo f; 

有很多这样的C代码,将结构声明为types。 如果这样的代码被迁移到C ++,那么typedefs会变得重复,因为C ++语言添加了它自己的隐式typedef。 所以,为了避免程序员将这些不再需要的typedef移除的麻烦,他们从一开始就允许在C ++中使用重复的typedefs。

正如其他人所说,有时间的人认识到,允许在C中重复相同的typedef也是有用的。 至less,这不应该伤害。 这就是为什么这个C ++特性有一些“backported”到C11的原因。