- 1. 程序的翻译
- 1.1. 预处理
- 1.2. 编译
- 1.3. 汇编
- 1.4. 链接
- 2. 动态链接和静态链接
- 2.1. 动态链接
- 2.2. 静态链接
- 2.3. 动态链接和静态链接的区别
- 2.4. 动态链接和静态链接的验证
- 3. make/makefile
- 3.1. 依赖方法和依赖关系
- 3.2. 写一个makefile文件
- 3.3. PHONY的意义
- 3.4. makefile的推导规则
- 4. Linux下的第一个小程序
- 4.1. 缓冲区概念
- 4.2. 小程序代码
1. 程序的翻译
在Linux中,gcc/g++是一个c/c++编译器。下面我们从程序的翻译过程来了解gcc工具的应用。程序的翻译分为以下的4步:
- 预处理
- 编译
- 汇编
- 链接
1.1. 预处理
程序翻译的第一步,就是做预处理工作。在Linux中,我们可以利用gcc创建一个文件来查看预处理的过程。
命令: gcc -E 文件名 -o 你想取的文件名
功能:翻译一个文件,当做完预处理阶段,就停下来。 -o选项是生成临时的文件。
示范:
其中,我们要知道程序翻译的过程中,预处理部分是做了什么工作的?
- 程序的预处理:
- 头文件展开
- 去注释
- 宏替换
- 条件编译
那么我们知道了预处理是做了以上工作的,我们来打开test.i文件看看是不是这样的?
我们可以看到,通过gcc工具,输入相应的指令,我们可以看到程序翻译的预处理部分。
1.2. 编译
编译的工作主要是把C语言转换成相应的汇编语言。下面我们通过gcc工具来看一看过程。
命令: gcc -S 文件名 -o test.s
功能: 从现在开始,进行程序的翻译,做完编译工作,变成汇编之后,就停下来。
示范:
打开新形成的文件,我们可以看到里面的一些汇编代码。
1.3. 汇编
汇编这一步的主要功能就是将汇编代码变成二进制文件(不可执行)。
命令: gcc -c 文件名 -o test.o
功能:从现在开始,进行程序的翻译,完成汇编工作后,形成一个不可执行的二进制文件
示范:
打开test.o文件,我们可以看到里面的内容。
1.4. 链接
链接这一过程结束后,就形成了可执行程序(可执行的二进制程序)。主要内容就是库 + 你的代码。
命令: gcc 文件名 -o 你想取的文件名
功能:将汇编的文件与库链接,形成可执行的二进制程序。
示范:
通过执行该文件,我们可以相当于完成了程序的翻译。
2. 动态链接和静态链接
2.1. 动态链接
在日常的一天,你对自己接下来的一整天都做了一份计划,例如,吃早餐,看书,看剧,…
你是一名高中生,家里没有电脑,但是你这时候给自己的安排是打电脑游戏。那怎么办呢?
办法当然是去网吧咯,在网吧里,里面的机子都可以满足你对电脑的需求。
那么把角度切换回程序的世界。你对你接下来的一整天都做好的计划就是你的程序,当你的程序运行到打电脑游戏的时候,你才会动身去网吧使用电脑。
就好像你的代码需要某个功能的实现,例如打印,刚好库里printf就能实现你的需求。
这个动态就类似让你动起来。
其中你是怎么知道网吧的位置呢?那肯定是找人问的咯。同样的,切换回程序的世界,程序是怎么知道库的存在呢?答案是编译器的存在,编译器可以认为是你找网吧时问的那个人,通过那个人你找到了网吧,程序通过编译器找到了标准库。
2.2. 静态链接
回到上面的例子,某一天父母发现你对电脑的需求,所以在家里给你配了一台电脑,从此以后你用电脑的时候就不再需要跑去网吧了,而是在家使用电脑。切换回程序的世界,当你自己亲手写了一个函数,当你的程序运行到某一步时,就不需要调用标准库里的函数,而是直接可以使用你写的代码。
2.3. 动态链接和静态链接的区别
- 动态链接
- 如果一个网吧要升级,你觉得你还能去上网吗?答案肯定是不能的。同样的道理,如果一个库要升级,你就不能使用这个库的代码了。
- 如果网吧被取缔了,那么你就没得上网咯。同样的,如果一个库被取缔,你也不能使用这个库的代码了。
- 从你家里到一家网吧是需要一段路程的,那么也就是说是要花费时间的。同样的,动态链接也是需要时间的。
动态链接的优势:
- 形成的可执行程序小,主要体现在内存,磁盘,网络方面。如果你的软件程序体积小,那么内存加载就会快;同样的在磁盘中所需要占用的空间也小;同样的网络上下载文件,如果体积小,那么下载的速度也会快很多。就好比你去网吧,在电脑方面上的东西,你基本都不用带,人过去就行;切回代码的角度,就是你直接调用库代码即可。
- 静态链接
- 家里有电脑,我根本就不关心网吧要不要升级。同样的,我自己写了一个代码,跟库里的一模一样,那么我就用自己的代码就好啦。
- 网吧被取缔了,机器都没了,但是与我无关,因为我有家里的电脑。如果因为某些原因,不小心把库给删除了,我依旧能用我的代码。
- 时间问题,由于我家里有电脑,我根本就不需要出门,所以时间上的花费就少了很多。
静态链接的优势:
- 不受库的影响,种种优点就是上面所说的。但缺点也明显,就是形成的可执行程序体积太大!
2.4. 动态链接和静态链接的验证
- 动态库在Linux下的命名
一般是libXXXXXX.so,其中lib是前缀,.so是后缀,去掉前缀和后缀,中间的就是库的名称。
- 静态库在Linux下的命名
一般是libXXXXXX.a,其中lib是前缀,.so是后缀,去掉前缀和后缀,中间的就是库的名称。
- 使用ldd + 文件名即可查看你文件中的代码使用了哪个库。
动态链接的验证
file + 可执行程序的文件名即可查看文件的链接方式。
其中你也可以在使用gcc工具对文件进行编译的时候,指定文件使用静态链接。操作如下:
3. make/makefile
make是一个命令,makefile是一个文件。
用一个例子来解释,就好比厂里有一台机子,你在操作台上面提前设置好动作,让机器实现自动化。
切换会代码的角度,就是创建好一个makefile文件,里面的内容是关于你接下来的动作,例如编译代码。
makefile文件中的指令:
3.1. 依赖方法和依赖关系
用生活中的例子来解释,假如你是一个学生,你生病了要请假,那么就要向班主任说明你的情况并作出申请,然后班主任同意了即可请假离校。其中学生和班主任是依赖关系,你一个学生总不能向你的好基友请假吧。所谓的依赖方法就是请假的程序,向班主任申请,班主任同意,离校…
切换回代码的角度,我要形成可执行程序,我要依赖源文件。依赖方法就是我接下来要怎么做,其中上图的依赖方法可以看到,我是要用gcc工具来编译形成可执行程序。
3.2. 写一个makefile文件
如何编写一个makefile文件呢?
- 在第一行中,冒号是关键(:) ,冒号的左边是形成的目标文件,冒号的右边是依赖文件(可有可无)
- 在接下来的第二行,先有一个table的间隔,然后开始编写依赖方法。
- 如图所示:
3.3. PHONY的意义
我现在写好了一个伪目标makefile文件如下:
上面的test没有用PHONY修饰,那么只要vim里面的代码没有任何改动,那么make命令就不会执行。如下:
但是用了PHONY修饰的指令,会一直执行。如下:
我把test也加上PHONY修饰。
执行make命令如下:
那么g++是怎么知道文件到底更没更新呢?先来认识一下文件的三个时间
其中make命令就是根据依赖的文件与目标文件形成时间的先后来判断是否需要更新。
3.4. makefile的推导规则
首先我们看到上面的makefile的文件内容是直接生成可执行程序的,但是可执行程序是怎么来的呢?同样的也是经过程序的翻译才能得到的可执行程序。所以根据刚才所说的,我们可以知道实际的makefile内容应当是这样的:
- 先依赖test.o文件,但是没有,所以进入到下一层找
- 依赖test.s文件,没有,进入下一层
- 依赖test.i文件,没有,进入下一层
- 依赖test.c文件,有,形成test.i文件
- 形成test.i文件后,逐一返回上层执行命令。
一句话总结,你可以理解为是数据结构中的栈结构,也就是先进后出。
4. Linux下的第一个小程序
4.1. 缓冲区概念
下面来看两个代码
其中代码的不同就是少了一个换行符,在语言层面,换行符其实就相当于行缓冲。
第一个代码因为有了行缓冲,所以printf里面的内容直接被刷新。所以是先打印内容,再等待2s。
第二个代码因为没有行缓冲,所以printf里面的内容没有直接被刷新,而是先等待2s,然后再从缓冲区里面打印出来。
4.2. 小程序代码
利用上面的缓冲区概念,我们可以利用这个特性,做出一个倒计时的小程序。
1 #include "process.h"
2 void Process()
3 {
4 char s[NUM];
5 memset(s,'\0',sizeof(s));
6 int cnt = 0;
7 char ch[4] = {'|','\\','/','-'};
8 while(cnt <= 100)
9 {
10 printf("[%-100s][%d%%][%c]\r",s,cnt,ch[cnt%4]);
11 fflush(stdout);
12 s[cnt++] = STR;
13 usleep(50000);
14 }
15 printf("\n");
16 }
效果可以自行运行代码查看,很有趣的小程序。