从extern关键字开始谈C语言多文件编程

在前面的教程中,我们都是将所有的代码写到一个源文件里面,对于小程序,代码不过几百行,这或许无可厚非,但当程序膨胀代码到几千行甚至上万行后,就应该考虑将代码分散到多个文件中,否则代码的阅读和维护将成为一件痛苦的事情。

本节我们就来演示一下多文件编程。在下面的例子中,我们创建了两个源文件 main.c 和 module.c:
  • module.c 是整个程序的一个模块,我们在其中定义了一个全局变量和一个函数;
  • main.c 是程序的主模块(主文件),它使用到了 module.c 中的变量和函数。

module.c 源码:
#include <stdio.h>

int m = 100;

void func(){
    printf("Multiple file programming!\n");
}
main.c 源码:
#include <stdio.h>

extern void func();
extern int m;

int n = 200;

int main(){
    func();
    printf("m = %d, n = %d\n", m, n);
    return 0;
}
在 Visual Studio 中,将两个源文件都添加到工程中,点击“运行(Run)”按钮就可以运行程序。

在 Linux GCC 中,可以使用下面的命令来编译和运行程序:

$gcc main.c module.c
$./a.out

程序最终的运行结果为:

Multiple file programming!
m = 100, n = 200


m 和 n 是在所有函数之外定义的全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的代码文件,包括.c.h文件。
如果你一直在编写单个源文件的程序,那么请注意,全局变量的作用范围不是从变量定义处到该文件结束,在其他文件中也有效。
这里需要重点理解的是 extern 关键字,它用来声明一个变量或函数。

extern 关键字

我们知道,C语言代码是由上到下依次执行的,不管是变量还是函数,原则上都要先定义再使用,否则就会报错。但在实际开发中,经常会在函数或变量定义之前就使用它们,这个时候就需要提前声明。

所谓声明(Declaration),就是告诉编译器我要使用这个变量或函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。

例如,我们知道使用 printf()、puts()、scanf()、getchar() 等函数要引入 stdio.h 这个头文件,很多初学者认为 stdio.h 中包含了函数定义(也就是函数体),只要有了头文件程序就能运行。其实不然,头文件中包含的都是函数声明,而不是函数定义,函数定义都在系统库中,只有头文件没有系统库在链接时就会报错,程序根本不能运行。

1) 函数的声明

在《C语言函数声明以及函数原型》一节中我们讲到了函数声明,那时并没有使用 extern 关键字,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有 extern 都是一样的。

总结起来,函数声明有四种形式:

//不使用 extern
datatype function( datatype1 name1, datatype2 name2, ... );
datatype function( datatype1, datatype2, ... );
//使用 extern
extern datatype function( datatype1 name1, datatype2 name2, ... );
extern datatype function( datatype1, datatype2, ... );

2) 变量的声明

变量和函数不同,编译器只能根据 extern 来区分,有 extern 才是声明,没有 extern 就是定义。

变量的定义有两种形式,你可以在定义的同时初始化,也可以不初始化:

datatype name = value;
datatype name; 

而变量的声明只有一种形式,就是使用 extern 关键字:

extern datatype name;

另外,变量也可以在声明的同时初始化,格式为:

extern datatype name = value;

这种似是而非的方式是不被推荐的,有的编译器也会给出警告,我们不再深入讨论,也建议各位读者把定义和声明分开,尽量不要这样写。

extern 是“外部”的意思,很多教材讲到,extern 用来声明一个外部(其他文件中)的变量或函数,也就是说,变量或函数的定义在其他文件中。

不过我认为这样讲不妥,因为除了定义在外部,定义在当前文件中也是正确的。例如,将 module.c 中的int m = 100;移动到 main.c 中的任意位置都是可以的。所以我认为,extern 是用来声明的,不管具体的定义是在当前文件内部还是外部,都是正确的。

关  闭