变量和函数的定义和声明 (四)

2014-11-24 03:18:34 · 作者: · 浏览: 4
个模块怎么办呢?如果在foo.c里面也#include "stack.c",就相当于push、pop、is_empty这三个函数在main.c和foo.c中都有定义,那么main.c和foo.c就不能链接在一起了。如果采用包含头文件的办法,那么这三个函数只在stack.c中定义了一次,最后可以把main.c、stack.c、foo.c链接在一起。如下图所示:

图 20.2. 为什么要包含头文件而不是.c文件

为什么要包含头文件而不是.c文件



同样道理,头文件中的变量和函数声明一定不能是定义。如果头文件中出现变量或函数定义,这个头文件又被多个.c文件包含,那么这些.c文件就不能链接在一起了。

2.3. 定义和声明的详细规则
以上两节关于定义和声明只介绍了最基本的规则,在写代码时掌握这些基本规则就够用了,但其实C语言关于定义和声明还有很多复杂的规则,在分析错误原因或者维护规模较大的项目时需要了解这些规则。本节的两个表格出自[Standard C]。

首先看关于函数声明的规则。

表 20.1. Storage Class关键字对函数声明的作用

Storage Class File Scope Declaration Block Scope Declaration
none previous linkage
can define
previous linkage
cannot define

extern previous linkage
can define
previous linkage
cannot define

static internal linkage
can define
N/A

以前我们说“extern关键字表示这个标识符具有External Linkage”其实是不准确的,准确地说应该是Previous Linkage。Previous Linkage的定义是:这次声明的标识符具有什么样的Linkage取决于前一次声明,这前一次声明具有相同的标识符名,而且必须是文件作用域的声明,如果在程序文件中找不到前一次声明(这次声明是第一次声明),那么这个标识符具有External Linkage。例如在一个程序文件中在文件作用域两次声明同一个函数:

static int f(void); /* internal linkage */
extern int f(void); /* previous linkage */则这里的extern修饰的标识符具有Interanl Linkage而不是External Linkage。从上表的前两行可以总结出我们先前所说的规则“函数声明加不加extern关键字都一样”。上表也说明了在文件作用域允许定义函数,在块作用域不允许定义函数,或者说函数定义不能嵌套。另外,在块作用域中不允许用static关键字声明函数。

关于变量声明的规则要复杂一些:

表 20.2. Storage Class关键字对变量声明的作用

Storage Class File Scope Declaration Block Scope Declaration
none external linkage
static duration
static initializer
tentative definition
no linkage
automatic duration
dynamic initializer
definition

extern previous linkage
static duration
no initializer[*]
not a definition
previous linkage
static duration
no initializer
not a definition

static internal linkage
static duration
static initializer
tentative definition
no linkage
static duration
static initializer
definition

上表的每个单元格里分成四行,分别描述变量的链接属性、生存期,以及这种变量如何初始化,是否算变量定义。链接属性有External Linkage、Internal Linkage、No Linkage和Previous Linkage四种情况,生存期有Static Duration和Automatic Duration两种情况,请参考本章和上一章的定义。初始化有Static Initializer和Dynamic Initializer两种情况,前者表示Initializer中只能使用常量表达式,表达式的值必须在编译时就能确定,后者表示Initializer中可以使用任意的右值表达式,表达式的值可以在运行时计算。是否算变量定义有三种情况,Definition(算变量定义)、Not a Definition(不算变量定义)和Tentative Definition(暂定的变量定义)。什么叫“暂定的变量定义”呢?一个变量声明具有文件作用域,没有Storage Class关键字修饰,或者用static关键字修饰,那么如果它有Initializer则编译器认为它就是一个变量定义,如果它没有Initializer则编译器暂定它是变量定义,如果程序文件中有这个变量的明确定义就用明确定义,如果程序文件没有这个变量的明确定义,就用这个暂定的变量定义[32],这种情况下变量以0初始化。在[C99]中有一个例子:

int i1 = 1; // definition, external linkage
static int i2 = 2; // definition, internal linkage
extern int i3 = 3; // definition, external linkage
int i4; // tentative definition, external linkage
static int i5; // tentative definition, internal linkage
int i1; // valid tentative definition, refers to previous
int i2; // 6.2.2 renders undefined, linkage disagreement
int i3; // valid tentative definition, refers to previous
int i4; // valid tentative definition, refers to previous
int i5; // 6.2.2 renders undefined, linkage disagreement
extern int i1; // refers to previous, whose linkage is external
extern int i2; // refers to previous, whose linkage is internal
extern int i3; // refers to previous, whose linkage is external
extern int i4; // refers to previous, whose linkage is external
extern int i5; // refers to previous, whose linkage is internal变量i2和i5第一次声明为Internal Linkage,第二次又声明为External Linkage,这是不允许的,编译器会报错。注意上表中标有[*]的单元