在讲解 extern 和 static 关键字的时候,我们已经给出了几个简单的多文件编程的例子,现在不妨再看一个例子。
main.c 源码:
#include <stdio.h>
#include <conio.h>
// 也可以不写 extern;为了程序可读性,建议写上
extern long sum(int, int);
// 必须写 extern
extern char* OS;
int main()
{
int n1 = 1, n2 = 100;
printf("从%d加到%d的和为%ld [By %s]", n1, n2, sum(n1, n2), OS);
getch();
return 0;
}
module.c 源码:
#include <stdio.h>
// 当前操作系统
char *OS = "Windows 7";
long sum(int fromNum, int endNum){
int i;
long result = 0;
// 参数不符合规则,返回 -1
if(fromNum<0 || endNum<0 || endNum<fromNum){
return -1;
}
for(i=fromNum; i<=endNum; i++){
result += i;
}
// 返回大于等于0的值
return result;
}
运行结果:从1加到100的和为5050 [By Windows 7]
这个程序,我们按照“编译 --> 链接 --> 运行”的步骤来生成,不要按 F5 或者 F7(对于 Visual C++)直接生成 exe。如果已经生成,可以清理掉相关文件。
注意:一个程序有且只能有一个 main() 函数,即使它有多个源文件。main() 函数是程序的入口函数,双击运行程序时就从这里开始执行。
对于C,首先要把源文件编译(Compile)成目标文件(Object File),也就是Windows下的 .obj 文件。然后再把单个或多个 Object File 合并成可执行文件,也就是Windows下是 .exe 文件,这个动作叫作链接(Link)。
编译时,编译器需要检查语法是否正确,函数、变量的声明是否正确;只有函数、变量的声明但没有定义是完全正确的。函数声明是告诉编译器该函数已经存在,但是入口地址还未确定,暂时在此做个标记,链接时编译器会找到函数入口地址,并将标记替换掉。
这些都校验通过,编译器就可以编译出中间目标文件。一般来说,编译是针对单个源文件的,多个源文件需要编译多次,每个源文件都会生成一个对应的目标文件。
编译产生的 .obj 文件已经是二进制文件,与 .exe 的组织形式类似,只是有些函数的入口地址还未找到,程序不能执行。链接的作用就是找到函数入口地址,将所有的源文件组织成一个可以执行的二进制文件。
链接时,主要是链接函数和全局变量。链接器并不管函数或变量所在的源文件,只管中间目标文件(Object File)。
总结一下:源文件首先会生成中间目标文件,再由中间目标文件生成可执行文件。在编译时,编译器只检测程序语法、函数声明、变量声明是否正确。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是 Link 2001 错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File。
知道了编译链接的原理,接下来我们使用VC一步步的生成 .exe 文件。
首先切换到 main.c 面板,按下 Ctrl+F7 键(编译),打开项目目录下的 Release 或 Debug 文件夹,可以看到多出了一个 main.obj 文件,这就是 main.c 生成的目标文件。
再切换到 module.c 面板,进行同样的操作,会生成 module.obj 文件,至此编译完毕(所有的源文件都编译过了)。
最后,按下 F7 键(链接),就生成了 main.exe,双击运行就可以看到上面的输出结果了。