外部及试探性定义
在翻译单元的顶层(及在预处理器后拥有所有 #include 的源文件),每个 C 程序都是声明的序列,它们声明函数和拥有外部链接的对象。这些声明被称作外部声明,因为它们出现于任何函数的外部。
extern int n; // 外部声明拥有外部链接 int b = 1; // 外部定义拥有外部链接 static const char *c = "abc"; // 外部定义拥有内部链接 int f(void) { // 外部定义拥有外部链接 int a = 1; // 非外部 return b; } static void x(void) { // 外部定义拥有内部链接 }
声明为拥有外部声明的对象拥有静态存储期,从而不能使用 auto
或 register
指定符。这些由外部声明引入的标识符拥有文件作用域。
试探性定义
试探性定义是没有初始化器的声明,且要么没有存储类指定符或拥有指定符 static
。
试探性定义是可能或可能不表现为定义的声明。若在同一翻译单元的前方或后方能找到实际的外部定义,则试探性定义仅表现为声明。
int i1 = 1; // 定义,外部链接 int i1; // 试探性定义,表现为声明,因为 i1 已定义 extern int i1; // 声明,引用前面的定义 extern int i2 = 3; // 定义,外部链接 int i2; // 试探性定义,表现为声明,因为 i2 已定义 extern int i2; // 声明,引用到前面的外部链接定义
若在同一翻译单元中无定义,则试探性定义表现为拥有初始化器 = 0 (对于数组、结构、联合类型则是 = {0} )的实际定义。
int i3; // 试探性定义,外部链接 int i3; // 试探性定义,外部链接 extern int i3; // 声明,外部链接 // 在此翻译单元中, i3 被如同“ int i3 = 0; ”的方式定义
不同于 extern 声明,若前一声明已建立标识符链接;则 extern 声明不更改链接,而试探性定义可以与同一标识符另一声明的链接不一致。若同一标识符的二个声明均在作用域内且拥有不同链接,则行为未定义:
static int i4 = 2; // 定义,内部链接 int i4; // 未定义行为:链接与前一行不一致 extern int i4; // 声明,引用到内部链接定义 static int i5; // 试探性定义,内部链接 int i5; // 未定义行为:链接与前一行不一致 extern int i5; // 引用到前者,其链接为内部
拥有内部链接的试探性定义必须拥有完整类型。
static int i[]; // 错误:试探性 static 声明中的不完整类型 int i[]; // OK,等价于 int i[1] = {0}; 除非在此文件之后重声明
一个定义规则
每个翻译单元可以拥有每个具有内部链接( static
全局名称)的零或一个外部定义。
若一个具有内部链接的标识符被用于任何异于非 VLA 的 (C99 起) sizeof ,或 _Alignof (C11 起)的表达式,则在该翻译单元中必须有且只有一个该标识符的外部定义。
整个程序可以拥有每个具有外部链接的标识符(异于 inline 函数) (C99 起)的零或一个外部定义。
若一个具有外部链接的标识符被用于任何异于非 VLA 的 (C99 起) sizeof ,或 _Alignof (C11 起)的表达式,则在整个程序中必须有且只有一个该标识符的外部定义。
注意
inline 函数定义的细节见 inline 。
关键词 extern
与文件作用域中声明在一起的含义,见存储期及链接。
声明与定义间的区别见定义。
发明试探性定义是为了标准化各种 C89 前的前置声明具有内部链接标识符的手段。
引用
- C11 standard (ISO/IEC 9899:2011):
- 6.9 External definitions (p: 155-159)
- C99 standard (ISO/IEC 9899:1999):
- 6.9 External definitions (p: 140-144)
- C89/C90 standard (ISO/IEC 9899:1990):
- 3.7 EXTERNAL DEFINITIONS