目录
编写C程序
在Windows下我们可以使用各种各样的IDE进行编程,比如强大的Visual Studio。Ubuntu下也有一些可以进行编程的工具,但是大多都只是编辑器,也就是只能进行代码编辑,如果要编译的话就需要用到GCC编译器,使用GCC编译器肯定就要接触到Makefile。
编译C程序
使用gcc编译器编译C程序。
gcc [选项] [文件名]
-c:只编译不链接为可执行文件,编译器将输入的c文件编译为.o的目标文件。
-o:<输出文件名>用来指定编译结束以后的输出文件名,如果不使用这个选项的话 GCC默认编译出来的可执行文件名字为a.out。
-g:添加调试信息,如果要使用调试工具(如GDB)的话就必须加入此选项,此选项指示编译的时候生成调试所需的符号信息。
-o:对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进行优化,这样产生的可执行文件执行效率就高。
-o2:比-o更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。
编译流程
GCC编译器的编译流程是:预处理、编译、汇编和链接。预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。编译是将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。汇编就是将汇编语言文件编译成二进制目标文件。链接就是将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。上一小节演示的例程都只有一个文件,而且文件非常简单,因此可以直接使用gcc命令生成可执行文件,并没有先将c文件编译成.o文件,然后再链接在一起。
Makefile基础
通过在终端执行gcc命令来完成C文件的编译,如果我们的工程只有一两个C文件还好,需要输入的命令不多,当文件有几十、上百甚至上万个的时候用终端输入GCC命令的方法显然是不现实的。如果我们能够编写一个文件,这个文件描述了编译哪些源码文件、如何编译那就好了,每次需要编译工程的时只需要使用这个文件就行了。这种问题怎么可能难倒聪明的程序员,为此提出了一个解决大工程编译的工具: make,描述哪些文件需要编译、哪些需要重新编译的文件就叫做Makefile,Makefile 就跟脚本文件一样,Makefile里面还可以执行系统命令。使用的时候只需要一个make命令即可完成整个工程的自动编译,极大的提高了软件开发的效率。如果大家以前一直使用IDE来编写C语言的话肯定没有听说过 Makefile这个东西,其实这些IDE是有的,只不过这些IDE对其进行了封装,提供给大家的是已经经过封装后的图形界面了,我们在IDE中添加要编译的C文件,然后点击按钮就完成了编译。在Linux下用的最多的是GCC编译器,这是个没有UI的编译器,因此Makefile就需要我们自己来编写了。因为在Linux下你不得不懂Makefile,再就是通过Makefile你就能了解整个工程的处理过程。
Makefile规则格式
目标:依赖文件集合
命令1
命令2
......
比如下面这条规则:
main: main.o inupt.o calcu.o
gcc -o mian mian.o inupt.o calcu.o
这条规则的目标是main, main.o、 input.o 和 calcu.o 是生成main 的依赖文件,如果要更新目标,main,就必须要先更新它的所有依赖文,如果依赖文件中的任何一个有更新,那么目标也必须更新, “更新”就是执行一遍规则中的命令列表。
命令列表中的每条命令必须以TAB键开始,不能使用空格!
make命令会为Makefile中的每个以TAB开始的命令创建一个Shell进程去执行。
main: main.o iput.o calcu.o
gcc -o main main.o iput.o calcu.o
main.o: main.c
gcc -c main.c
iput.o: iput.c
gcc -c iput,c
calcu.o:calcu,c
ccc -c calcu,c
clear:
rm *o
rm main
上述代码中一共有5条规则,1~2行为第一条规则,3~4行为第二条规则,5~6 行为第三条规则, 7-8行为第四条规则, 10-12为第五条规则, make命令在执行这个Makefile的时候其执行步骤如下:
首先更新第一条规则中的main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile的工作,完成了整Makefile就是为了完成这个工作。在第一次编译的时候由于main还不存在,因此第一条规则会执行,第一条规则依赖于文件main.o、input.o和calcu.o这个三个.o文件,这三个.o文件目前还都没有,因此必须先更新这三个文件。make会查找以这三个.o文件为目标的规则并执行。以main.o为例,发现更新main.o的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为"gcc-c main.c",这行命令很熟悉了吧,就是不链接编译main.c,生成main.o,其它两个.o文件同理。最后一个规则目标是clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行clean的话可以直接使用命令"make clean”,执行以后就会删除当前目录下所有的.o文件以及main,因此clean的功能就是完成工程的清理, "make clean”的执行过程如图所示:
可以看出,当执行"make clean”命令以后,前面编译出来的.0和main可执行文件都被删除掉了,也就是完成了工程清理工作。
总结Make的执行过程:
1.make命令会在当前目录下查找以Makefile(makefile其实也可以)命名的文件。
2.当找到Makefile文件以后就会按照Makefile中定义的规则去编译生成最终的目标文件。
3.当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。
这就是make的执行过程,make工具就是在Makefile中一层一层的查找依赖关系,并执行·相应的命令。编译出最终的可执行文件。Makefile的好处就是“自动化编译”,一旦写好了Makefile文件,以后只需要一个make命令即可完成整个工程的编译,极大的提高了开发效率。
Makekile变量
跟C语言一样Makefile也支持变量的,先看一下前面的例子:
main: main.o iput.o calcu.o
gcc -o main main.o iput.o calcu.o
上述 Makefile 语句中, main.o input.o 和 calcue.o 这三个依赖文件,我们输入了两遍,我们这个 Makefile比较小,如果Makefile 复杂的时候这种重复输入的工作就会非常费时间,而且非常容易输错,为了解决这个问题, Makefile加入了变量支持。不像C语言中的变量有int、char等各种类型, Makefile中的变量都是字符串!类似C语言中的宏。使用变量将上面的代码修改,修改以后如下所示:
在“示例代码中在定义变量objects的时候使用“=”对其进行了赋值, Makefile变量的赋值符还有其它两个“:=”和“?=”,我们来看一下这三种赋值符的区别:
赋值符”=”
使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,比如如下代码:
赋值符”:=”
因为赋值符“:二”不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。
赋值符”?=”
意思就是,如果变量temp2前面没有被赋值,那么此变量就是“yuan”,如果前面已经赋过值了,那么就使用前面赋的值。
变量追加”+=”
Makefile中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进。此时就要使用到符号“+=”,比如如下所示代码:
Makefile模式规则
main: main.o iput.o calcu.o
gcc -o main main.o iput.o calcu.o
main.o: main.c
gcc -c main.c
iput.o: iput.c
gcc -c iput,c
calcu.o:calcu,c
ccc -c calcu,c
clear:
rm *o
rm main
述Makefile中第 3-8行是将对应的.c 源文件编译为.o文件,每一个C文件都要写一个对应的规则,如果工程中C文件很多的话显然不能这么做。为此,我们可以使用Makefile中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c文件编译为对应的.o文件。
模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c结尾的文件,类似与通配符,a.%.c就表示以a.开头,以.c 结束的所有文件。
当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:
Makefile 自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如表:
Makefile伪目标
Makefile 有一种特殊的目标—伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。使用伪目标主要是为了避免Makefile中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如前面的代码清理工程的功能。
上述规则中并没有创建文件clean的命令,因此工作目录下永远都不会存在文件clean,当我们输入"make clean”以后,后面的"rm*.o”总是会执行。可是如果我们"手贱”,在工作目录下创建一个名为"clean”的文件,那就不一样了,当执行"make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的rm命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将clean声明为伪目标,声明方式如下:
.PHONY
Makefile条件判断
在c语言中我们通过条件判断语句来根据不同的情况来执行不同的分支, Makefile也支持条件判断,语法有两种如下:
其中条件关键字有4个:ifeq、ifneq、ifdef 和 ifndef,这四个关键字其实分为两对ifeq 与ifneq、 ifdef 与 ifndef,先来看一下ifeq和ifneq, ifeq用来判断是否相等,ifneq 就是判断是否不相等,ifeq用法如下:
上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和.“参数2”可以为函数返回值。ifneq的用法类似,只不过 ifneq是用来了比较“参数1”和“参数 2”是否不相等,如果不相等的话就为真。
ifdef和ifndef 的用法如下:
ifdef <变量名>
如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是一个函数的返回值。ifndef用法类似,但是含义用户ifdef相反。