外部及试探性定义

< c‎ | language

翻译单元的顶层(及在预处理器后拥有所有 #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) { // 外部定义拥有内部链接
}

声明为拥有外部声明的对象拥有静态存储期,从而不能使用 autoregister 说明符。这些由外部声明引入的标识符拥有文件作用域

试探性定义

试探性定义是没有初始化器的外部声明,且要么没有存储类说明符或拥有说明符 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 起)的表达式,则在该翻译单元中必须有且只有一个该标识符的外部定义。

整个程序可以拥有每个具有外部链接的标识符的零或一个外部定义。

若一个具有外部链接的标识符被用于任何异于非 VLA 的 (C99 起) sizeof ,或 _Alignof (C11 起)的表达式,则在整个程序中必须有且只有一个该标识符的外部定义。

注解

不同翻译单元中的内联定义不受一个定义规则约束。 inline 函数定义的细节见 inline

(C99 起)

关键词 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