定义
本节澄清三个容易混淆的术语:
- 声明 (Declaration)
- 定义 (Definition)
- 暂定定义 (Tentative definition)
1. 声明 vs 定义
1.1 声明
声明的作用是:把一个名字引入某个作用域,并为该名字绑定类型信息(以及可能的链接、存储期信息)。声明不一定分配存储。
例子:
c
extern int x; /* 声明:说明 x 在别处定义 */
int f(void); /* 声明:函数原型 */1
2
2
1.2 定义
定义是一种特殊的声明:它会为实体提供“实际内容”,通常意味着:
- 对对象:分配存储(并可能初始化)。
- 对函数:提供函数体。
例子:
c
int x = 1; /* 定义:分配存储并初始化 */
int f(void) { /* 定义:提供函数体 */
return x;
}1
2
3
4
2
3
4
extern + 初始化器
extern int x = 1; 仍然是定义:初始化器会使它成为定义。
2. 暂定定义(文件作用域)
在文件作用域,下面这种“没有初始化器且不带 extern”的对象声明,称为暂定定义:
c
int g; /* 暂定定义:外部链接 */
static int s; /* 暂定定义:内部链接 */1
2
2
一个翻译单元内可以有多个同名暂定定义;如果该翻译单元最终没有出现同名的“真正定义”(带初始化器的定义),那么这些暂定定义会合并为一个定义,并进行零初始化。
3. 常见陷阱
int x;在函数体内不是暂定定义:它是一个普通的定义(自动存储期)。- 把“带内部链接的定义”写进头文件会导致每个翻译单元各自拥有一份实体(尤其是
static对象),这往往不是你想要的。 - 不要在同一翻译单元里让同一标识符同时拥有内部链接与外部链接,否则是未定义行为。
4. 习题
- 判断:下面每一行是“声明”“定义”还是“暂定定义”?并说明理由。
c
extern int x;
int x;
int x = 1;
static int y;
static int y = 2;
int f(void);
int f(void) { return 0; }1
2
3
4
5
6
7
2
3
4
5
6
7
- 写一个最小示例(两个
.c文件):在a.c写int x;,在b.c写int x;,然后链接。解释会发生什么,为什么。 - 在一个翻译单元中,写出一个“多次暂定定义 + 最终无显式初始化器定义”的例子,并说明最终对象初值是什么。