外部及试探性定义
在翻译单元的顶层(及在预处理器后拥有所有 #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; // 声明,引用到前面的外部链接定义
若在同一翻译单元中无定义,则试探性定义表现为将对象空初始化的实际定义。
int i3; // 试探性定义,外部链接 int i3; // 试探性定义,外部链接 extern int i3; // 声明,外部链接 // 在此翻译单元中,如同以“ int i3 = 0; ”方式定义 i3
不同于 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 起) 或 typeof
(C23 起) 的表达式,则在该翻译单元中必须有且只有一个该标识符的外部定义。
整个程序可以拥有每个具有外部链接的标识符的零或一个外部定义。
若一个具有外部链接的标识符被用于任何异于非 VLA 的 (C99 起) sizeof
、_Alignof
(C11 起) 或 typeof
(C23 起) 的表达式,则在整个程序中必须有且只有一个该标识符的外部定义。
注解
不同翻译单元中的内联定义不受一个定义规则约束。内联函数定义的细节见 |
(C99 起) |
关键词 extern
与文件作用域中声明在一起的含义,见存储期及链接。
声明与定义间的区别见定义。
发明试探性定义是为了标准化各种 C89 前的前置声明具有内部链接标识符的手段。
引用
- C17 标准(ISO/IEC 9899:2018):
- 6.9 External definitions (第 113-116 页)
- C11 标准(ISO/IEC 9899:2011):
- 6.9 External definitions (第 155-159 页)
- C99 标准(ISO/IEC 9899:1999):
- 6.9 External definitions (第 140-144 页)
- C89/C90 标准(ISO/IEC 9899:1990):
- 3.7 EXTERNAL DEFINITIONS