为什么允许多次声明typedef标识符?

从C99标准来看,6.7(5):

声明指定一组标识符的解释和属性。 标识符的定义是该标识符的声明:对于对象,导致为该对象保留存储; 对于一个function,包括function体; 对于枚举常量或typedef名称,是标识符的(唯一)声明。

如果带有typedef标识符实际上是定义,那么为什么允许它们被多次声明? 例:

 int main() { typedef int x; typedef int x; } 

以上程序编译没有错误。 这怎么可能? 我期待该程序给我一个多重定义错误。

C99与C11不同

规则在C99和C11之间变化(并且C11规则与C ++规则匹配,据我所知)。 请注意,在这两个标准中,¶3在Constraints部分中,¶5在Semantics部分中。 这对于错误消息很重要 – 约束违规需要诊断。

ISO / IEC 9899:1999§6.7声明

¶3如果标识符没有链接,则除了6.7.2.3中指定的标记之外,标识符(在声明符或类型说明符中)的声明不应超过一个具有相同作用域和相同名称空间的声明。

5声明指定一组标识符的解释和属性。 标识符的定义是该标识符的声明:

  • 对于一个对象,导致为该对象保留存储;
  • 对于一个函数,包括函数体; 98)
  • 对于枚举常量或typedef名称,是标识符的(唯一)声明。

ISO / IEC 9899:2011§6.7声明

¶3如果标识符没有链接,则标识符的声明(在声明符或类型说明符中)不得超过一个具有相同作用域和相同名称空间的声明,但以下情况除外:

  • 如果类型不是可变修改类型,则可以重新定义typedef名称以表示与其当前相同的类型;
  • 标签可以按照6.7.2.3中的规定重新声明。

¶5声明指定一组标识符的解释和属性。 标识符的定义是该标识符的声明:

  • 对于一个对象,导致为该对象保留存储;
  • 对于一个函数,包括函数体; 119)
  • 对于枚举常量,是标识符的(唯一)声明;
  • 对于typedef名称,是标识符的第一个(或唯一的)声明。

Lundin 指出 ,标准C委员会的网站包含n1360 ,这是一份单页文件,详细说明了为何进行此项更改。 基本上:C ++就是这样做的; 一些编译器已经做到了; 既不难做也不颠覆任何东西以允许(要求)它。

海湾合作委员会并不总是执行标准

如果您的代码正在编译,那么它将根据C11规则或C ++规则进行编译。 它不是根据(严格)C99规则编译的。

请注意,除非您另有说明,否则GCC的最新版本允许重新定义,但也请注意John Bollinger的报告 ,即GCC 4.4.7(在未识别的平台上)不允许在C99模式下重新定义。

考虑文件retypedef.c

 int main(void) { typedef int x; typedef int x; xy = 0; return y; } 

使用GCC 4.9.1在Mac OS X 10.9.5上进行编译,得到:

 $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -c retypedef.c $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -pedantic -c retypedef.c $ gcc -O3 -g -std=c99 -Wall -Wextra -Werror -c retypedef.c $ gcc -O3 -g -std=c99 -Wall -Wextra -Werror -pedantic -c retypedef.c retypedef.c: In function 'main': retypedef.c:4:17: error: redefinition of typedef 'x' [-Werror=pedantic] typedef int x; ^ retypedef.c:3:17: note: previous declaration of 'x' was here typedef int x; ^ cc1: all warnings being treated as errors $ 

除非使用-pedantic ,否则它不会抱怨,只有在请求C99时才符合(在C11中重新定义同一范围内的typedef是标准的)。

您的程序编译没有错误,因为您的编译器是松散的(或编译为不同的标准)。 如果我使用gcc 4.4.7编译你的代码,那么它实际上确实报告了关于x的重新定义的错误。

出于同样的原因,允许多次声明其他声明。 例如:

 void foo(int a); void foo(int a); int main() { foo(42); } void foo(int a) { printf("%d\n", a); } 

这允许多个头部声明一个函数或结构,并允许两个或多个这样的头部包含在同一个转换单元中。

typedef类似于原型化函数或结构 – 它们声明具有一些编译时间含义的标识符,但没有运行时含义。 例如,以下内容无法编译:

 int main() { typedef int x; typedef int x; x = 42; } 

因为x没有命名变量; 它只是名称int的编译时别名。