本文我们来讲解一下在Linux中的C语言与C++进行编译的工具gcc与g++。
背景知识
首先,我们来介绍一下C语言中程序翻译的过程。在程序的翻译过程之中我们会遇到四个阶段:预处理、编译、汇编、链接。
- 预处理过程中进行的工作有头文件展开、条件编译、宏替换、去注释等,转换之后的文件还是以C语言为内容的文件;
- 编译过程中进行的工作是将C语言文件转变成汇编语言;
- 汇编过程中进行的工作是将汇编文件转变成可重定位二进制文件(不可以被执行)在Windows下形成的就是 bin.obj 文件。在此过程中将我们自己的代码进行翻译形成二进制文件;
- 链接过程中进我们自己形成的 .obj 文件和库文件合并,形成可执行的文件
然后,我们在Linux中看一下C文件的过程,我们使用vim编写一个简单的C程序:
#include <stdio.h>
#define M 100
#define Print
int main()
{
printf("%d\n", M);
/*
printf("%d\n", M);
printf("%d\n", M);
printf("%d\n", M);
printf("%d\n", M);
printf("%d\n", M);
printf("%d\n", M);
*/
#ifdef Print
printf("Print");
#else
printf("None");
#endif
return 0;
}
gcc编译C文件的过程:
使用gcc命令就可以编译上述的C文件:
给生成的文件命名 gcc -o
下面介绍gcc中的第一个选项 -o :后面紧跟执行后生成的名称,可以将原本生成的a.out文件进行重命名。
预编译 gcc -E
-E:从现在开始进行程序的翻译,预处理做完就停下来,形成.i文件。
gcc -E myfile.c -o myfile.i
使用vim打开myfile.i文件我们可以在最后找到经过预处理之后的代码和上面的代码进行比较,可以发现头文件展开、条件编译等等处理。
编译 gcc -S
-S:从现在开始进行程序的翻译,编译做完就停下来。形成.s文件
gcc -S myfile.i -o myfile.s
打开myfile.s文件可以看到原来的C文件已经变成了如下所示的汇编语言。
汇编 gcc -c
-c:从现在开始进行程序的翻译,汇编做完就停下来。形成.o文件,在windows中对应着obj文件
gcc -c myfile.s -o myfile.o
经过上述的步骤已经成功的将我们自己的代码翻译成了二进制文件。
将其赋予可执行的权限,可以发现并不能执行。
链接
gcc myfile.o -o myfile
最后进行 链接操作并执行文件。
链接的详细说明
先来介绍一个命令 ldd,这个命令可以查看文件形成是依赖了哪些库,以上述的文件为例子。
我们能够在Linux的环境下进行C、C++代码的编写与编译,就是因为Linux系统已经默认携带了语言级别的头文件与对应的库。
头文件
使用 ls /usr/include/ 指令就可以查看Linux下的头文件。
库
Linux下的库分为两种 - 本质来说也是一种文件。
- 静态库:libXXXXX.a(后面可能会携带版本号)
- 动态库:libXXXXX.so
一般在识别库的时候我们去掉前缀以及后缀 (前缀libXXXXX.a后缀)
上面的图中带有的库就是 c-2.17。
在Linux中的指令有一部分就是用C写的:
在上述的图片中,就可以找到C的库 。
静态库和动态库的知识点:
- 库分为静态库(专门让编译器,对用户的程序进行静态链接的)和动态库(专门让编译器,对用户的程序进行动态链接的)
- 静态库和静态链接∶链接的时候,如果是静态链接,找到静态库,拷贝静态库中的我所需要的代码到我自己的可执行程序中
- 动态库和动态链接︰链接的时候,如果是动态链接,找到动态库,拷贝动态库中的我所需要的代码的地址到我自己的可执行程序中相关的位置
- 静态链接成功:我们的程序,不依赖任何库,自己就可以独立运行
- 动态链接成功:我们的程序,还是依赖动态库,一旦动态库缺失,我们的程序便无法运行!
- 静态库,因为自身拷贝的问题,比较浪费空间
- 动态库:因为可以做到被大家共享方法,所以真正的实现永远都是在库中,程序内部只有地址,比较节省空间
- 静态库vs动态库: Linux默认使用的是动态链接和动态库!
对刚刚生成的文件进行file指令:
可以从中看到 dynamically linked这样的单词,因此就可以的得到使用的是动态库。
一般的Linux中没有静态库,可以使用 yum -y install glibc-static 命令来进行安装:
可以看到经过使用 -static 选项编译之后,得到的文件已经不再依赖于动态库,同样使用 file 指令可以参看文件的具体参数:
下面介绍一下Linux项目自动化构建工具make/Makefile
make/Makefile
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建
make是一个指令;makefile是一个文件
在当前的工作目录下生成一个Makefile文件,在其中编写如下的代码。
使用make即可以进行编译,然后使用make clean即可以清理生成的文件。
Makefile文件生成规则
下面来简要介绍一下Makefile生成的规则:Makefile是一个围绕依赖关系与依赖方法构建的自动化编译工具。完成一件事情必须要有正确的依赖关系与正确的依赖方法,下面就以上述C编译的例子来举例:
编写myfile文件需要myfile.o文件,这是一个依赖关系,需要 gcc myfile.o -o myfile 这个正确的依赖方法来生成.o文件。其余的依次都是这样。
依赖关系:
上面的文件
myfile,依赖 myfile.o
myfile.o ,依赖 myfile.s
myfile.s ,依赖 myfile.i
myfile.i ,依赖 myfile.c依赖关系中,目标文件对应的依赖文件列表可以为空。
依赖方法:
gcc myfile.* -option myfile.* ,就是与之对应的依赖关系
make命令会自动执行最三层的依赖关系与依赖方法,如果要使用后面的就需要在make后面添加上目标文件,下面的图片就是将clean与myfile进行位置交换后的结果:
.PHONY
多次执行make会有下面的提示,若是需要多次执行就可以使用命令 .PHONY clean 。这用来修饰 clean 表示总是被执行的。
假设当工程量很大时,编译的时间需要很久,因此不需要总是被执行。在clean中一般会添加这句指令。
make怎么能够判断自己需要被执行
有两条时间线,分别为C源文件与Makefile文件,先的是C文件,后有的是Makefile文件,当两者的修改时间发生变化,C文件的修改时间在Makefile文件的修改时间之后时,就会让make执行。