该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
保留目标代码、链接目标文件,用make工具自动化构建
我们已经学到了把程序分解成独立的源文件,不仅意味着可以在不同程序之间共享代码,还意味着可以开始建立真正的大程序;
把程序分解成更小的自洽代码片段,比起在一个庞大的源文件中搞定一切,现在你可以有很多更简单的文件,他们更容易理解、维护和测试;
使用多个源文件的优缺点都是可以开始创建一个大程序:
C编译器是很高效的软件,他把程序改头换面了好几次:编译器会修改源代码,把许许多多文件链接起来,还不会挤爆存储器,甚至还会优化代码,尽管做了很多事,但他还是很快;
多个文件创建的程序,编译时间往往很长,当只修改一行代码时,重新编译显然太慢;
(P1_1)
为了提高编译速度,我们需要想想办法。。。
如果只修改了一两个源文件便为程序重新编译所有源文件是很浪费的一件事;
gcc xxx.c xxx.c ... -o lanch
编译器会对所有的文件分别运行 预处理器、编译器 和 汇编器;没变的源代码对应的目标代码也不会改变,重新编译会生成相应源文件的目标代码,我们该做些什么呢?
让编译器把生成的目标代码保存到文件中,就不需要重新生成它了,除非你修改了源代码;
修改了源代码,可以重新创建这一个文件的目标代码,然后把所有的目标文件传给编译器,让编译器把他们链接起来;(不需要为其他文件创建目标代码,因为它们没变)
我们接下来要做的就是:如何让gcc把目标代码保存在文件中,让后让编译器把目标文件链接起来;
命令: gcc -c *.c
为所有文件创建目标代码,操作系统会把*.c替换成所有的c源文件的文件名;
*.c会匹配当前目录下的所有C源文件;
-c告诉编译器为所有源文件创建目标文件,但不把目标文件链接成完成的可执行程序;
命令:gcc *.o -o launch
把目标文件的名传给编译器链接起来;
编译器能够识别这些目标文件,因此会跳过大部分编译步骤,直接把目标文件链接为一个叫launch的可执行程序;
现在,我们得到了一个编译好的程序;还有一批源文件,可以在需要的时候随时把它们链接起来;
如果需要修改其中一个文件,只需要重新编译这一个文件;然后重新连接程序即可;
我们举一个例子:
(Code5_1)
5_1-launch.c
/*
* 程序入口 调用函数
*/
#include
#include "5_1-thruster.h"
int main() {
int a[] = {1,8,2,7,3,6,4,9};
printf("sum:%i\n", culculateSum(a,8));
return 0;
}
5_1-thruster.h
int culculateSum(int * a,int n);
5_1-thruster.c
/*
* 求和
*/
#include "5_1-thruster.h"
int culculateSum(int * a,int n){
int sum = 0;
for (int i = 0; i < n; i++) {
sum += *(a + i);
}
return sum;
}
bogon:0825-1 huaqiang$ gcc -c *.c
bogon:0825-1 huaqiang$ gcc *.o -o launch
bogon:0825-1 huaqiang$ ./launch
sum:40
运行命令及结果如上:
第一条命令执行之后,生成的目标文件分别是5_1-launch.o 5_1-thruster.o;
第二条命令生成了可执行程序launch;
现在我们修改了一个源文件,使用上面我们讲的方式,运行命令如下:
gcc -c thruster.c
gcc *.o -o launch
./launch
如果源文件很多,这样做编译时间将会节省很多,因为只编译了修改过的源文件;
(P1_2)
我们看如下示例,每个文件对应一个更新时间戳,我们来看看那些文件需要重新生成:
(P1_3)
显然,右侧的两个源文件需要重新编译,可执行程序需要重新链接生成;
现在问题来了:如果记不住修改了那些文件怎么办?
如果是发布最后的程序,我们可以重新编译每个文件,但是开发过程中,你绝对不想这样做;
有没有什么方法可以自定化这个过程呢?
make,我们的新朋友;其实只要记下修改过那些文件,就可以很快用gcc编译程序;这个过程很麻烦,但也容易自动化;
比如,从源文件到目标文件的过程,如果源文件较新,就需要重新编译,反过来则不需要;具体方式就是比较一下两个文件的时间戳;
make会检查源文件和目标文件的时间戳,如果目标文件过期,make就会重新编译它;
要做到这些,需要告诉make源代码的一些信息:
make需要知道文件之间的依赖关系,同时还需要告诉他你具体想如何构建代码;
make编译的文件叫目标target;(严格说来,make不仅仅可以用来编译文件)
目标可以是任何其他文件生成的文件,也就是说目标可以是一批文件压缩而成的压缩文档;
对于每个目标,make需要知道两件事:
依赖项:生成目标需要那些文件;
生成方法:生成该文件时需要用哪些指令;
依赖项和生成方法合在一起构成了一条规则;有了规则,make就知道如何生成目标;
make在Windows中另有其名,使用时请注意;
如果想把thruster.c编译成目标代码thruster.o:
thruster.o 就叫目标,因为想生成这个文件;
thruster.c 是依赖项,因为创建目标时需要它;
生成方法是转化时的编译命令:gcc -c thruster.c
这就是创建thruster.o的规则;就是说,只要告诉make依赖项及生成方法,就可以让make决定什么时候重新编译;
当然,也可以做的更多,创建了thruster.o之后,可以用它来创建launch程序:
launch文件设为目标;
依赖项是所有的.o文件;
生成方法 gcc *.o -o launch
有了这些之后,make会处理具体细节;
那么如何把依赖项和生成方法告诉make呢?(如何把描述好的规则告知make)
所有目标、依赖项和生成方法的细节信息都需要保存在一个叫makefile或Makefile的文件中;
我们使用代码:
(Code5_2)
5_2-launch.c
/*
* 程序入口 调用函数
*/
#include
#include "5_1-thruster.h"
int main() {
int a[] = {1,8,2,7,3,6,4,9};
printf("sum:%i\n", culculateSum(a,8));
return 0;
}
5_2-thruster.h
int culculateSum(int * a,int n);
5_2-thruster.c
/*
* 求和
*/
#include "5_1-thruster.h"
int culculateSum(int * a,int n){
int sum = 0;
for (int i = 0; i < n; i++) {
sum += *(a + i);
}
return sum;
}
launch程序需要由5_2-launch.o和5_2-thruster.o文件链接而成,这两个文件又是由相应的c文件和头文件编译而成;
5_2-launch.o还依赖与5_2-thruster.h文件
在makefile中的描述如下:
目标:依赖项
TAB 生成方法
5_2-launch.o:5_2-launch.c 5_2-thruster.h
gcc -c 5_2-launch.c
5_2-thruster.o:5_2-thruster.h 5_2-thruster.c
gcc -c 5_2-thruster.c
launch:5_2-launch.o 5_2-thruster.o
gcc 5_2-launch.o 5_2-thruster.o -o launch
注意:
-生成方法都必须以tab开头;
我们将make规则保存到当前目录的makefile文件中,让后执行命令:
bogon:0825-1 huaqiang$ make launch
紧接着看到控制台执行了如下命令:
gcc -c 5_2-launch.c
gcc -c 5_2-thruster.c
gcc 5_2-launch.o 5_2-thruster.o -o launch
可以看到make为了创建launch程序执行了一连串的命令;
现在我们修改5_2-thruster.c文件,并重新运行一次make,看看会发生什么:
bogon:0825-1 huaqiang$ make launch
gcc -c 5_2-thruster.c
gcc 5_2-launch.o 5_2-thruster.o -o launch
make会跳过创建新的5_2-launch.o这一步,只编译5_2-thruster.o,然后重新链接程序;
相比于ant和rake基于Java和Ruby的构建工具,make再试最早出现的用来从源代码自动构建程序的工具;
在Windows上写的makefile,在Mac或Linux上不一定能用,因为makefile可能会调用底层操作系统命令;
make除了编译代码,也可以充当命令行下的安装程序或源代码的控制工具;事实上,任何可以在命令行中执行的任务,你都可以用make来做;
有个叫autoconf的工具(http://www.gnu.org/software/autoconf)可以用来生成makefile,更加自动化些;
示例:
oggswing程序可以读取Ogg格式的音乐文件,然后创建一首歌曲,尝试写一下它的makefile:先编译oggswing,再用他来转换.ogg文件;
oggswing:oggswing.c oggswing.h
gcc oggswing.c -o oggswing
swing.ogg:whitennerdy.ogg oggswing
oggswing whitennerdy.ogg swing.ogg
-编译大量文件非常的耗时;
-可以把目标代码保存在*.o文件中,加快编译速度;
-gcc不但能从源文件,而且能从目标文件编译程序;
-make工具可以用来自动化代码的构建过程;
-make清楚文件之间的依赖关系,可以只编译那些修改过的文件;
-你需要使用makefile告诉make如何构建代码;
-处理makefile的格式时需要小心,别忘了用tab来缩进;
-char是值;
-小整数用short;
-普通整数用int;
-大整数用long;
-一般的浮点数用float;
-高精度的浮点数用double;
-函数的声明与定义分离;
-把声明放到头文件中;
-用#include<>包含标准库头文件;
-用#include""包含本地头文件;
-把目标代码保存到文件中,提高构建速度;
-使用make管理代码构建过程;