在Linux中,gcc/g++是一个c/c++编译器。下面我们从程序的翻译过程来了解gcc工具的应用。程序的翻译分为以下的4步:
程序翻译的第一步,就是做预处理工作。在Linux中,我们可以利用gcc创建一个文件来查看预处理的过程。
命令: gcc -E 文件名 -o 你想取的文件名
功能:翻译一个文件,当做完预处理阶段,就停下来。 -o选项是生成临时的文件。
示范:
其中,我们要知道程序翻译的过程中,预处理部分是做了什么工作的?
那么我们知道了预处理是做了以上工作的,我们来打开test.i文件看看是不是这样的?
我们可以看到,通过gcc工具,输入相应的指令,我们可以看到程序翻译的预处理部分。
编译的工作主要是把C语言转换成相应的汇编语言。下面我们通过gcc工具来看一看过程。
命令: gcc -S 文件名 -o test.s
功能: 从现在开始,进行程序的翻译,做完编译工作,变成汇编之后,就停下来。
示范:
打开新形成的文件,我们可以看到里面的一些汇编代码。
汇编这一步的主要功能就是将汇编代码变成二进制文件(不可执行)。
命令: gcc -c 文件名 -o test.o
功能:从现在开始,进行程序的翻译,完成汇编工作后,形成一个不可执行的二进制文件
示范:
打开test.o文件,我们可以看到里面的内容。
链接这一过程结束后,就形成了可执行程序(可执行的二进制程序)。主要内容就是库 + 你的代码。
命令: gcc 文件名 -o 你想取的文件名
功能:将汇编的文件与库链接,形成可执行的二进制程序。
示范:
通过执行该文件,我们可以相当于完成了程序的翻译。
在日常的一天,你对自己接下来的一整天都做了一份计划,例如,吃早餐,看书,看剧,…
你是一名高中生,家里没有电脑,但是你这时候给自己的安排是打电脑游戏。那怎么办呢?
办法当然是去网吧咯,在网吧里,里面的机子都可以满足你对电脑的需求。
那么把角度切换回程序的世界。你对你接下来的一整天都做好的计划就是你的程序,当你的程序运行到打电脑游戏的时候,你才会动身去网吧使用电脑。
就好像你的代码需要某个功能的实现,例如打印,刚好库里printf就能实现你的需求。
这个动态就类似让你动起来。
其中你是怎么知道网吧的位置呢?那肯定是找人问的咯。同样的,切换回程序的世界,程序是怎么知道库的存在呢?答案是编译器的存在,编译器可以认为是你找网吧时问的那个人,通过那个人你找到了网吧,程序通过编译器找到了标准库。
回到上面的例子,某一天父母发现你对电脑的需求,所以在家里给你配了一台电脑,从此以后你用电脑的时候就不再需要跑去网吧了,而是在家使用电脑。切换回程序的世界,当你自己亲手写了一个函数,当你的程序运行到某一步时,就不需要调用标准库里的函数,而是直接可以使用你写的代码。
动态链接的优势:
静态链接的优势:
一般是libXXXXXX.so,其中lib是前缀,.so是后缀,去掉前缀和后缀,中间的就是库的名称。
一般是libXXXXXX.a,其中lib是前缀,.so是后缀,去掉前缀和后缀,中间的就是库的名称。
动态链接的验证
file + 可执行程序的文件名即可查看文件的链接方式。
其中你也可以在使用gcc工具对文件进行编译的时候,指定文件使用静态链接。操作如下:
make是一个命令,makefile是一个文件。
用一个例子来解释,就好比厂里有一台机子,你在操作台上面提前设置好动作,让机器实现自动化。
切换会代码的角度,就是创建好一个makefile文件,里面的内容是关于你接下来的动作,例如编译代码。
makefile文件中的指令:
用生活中的例子来解释,假如你是一个学生,你生病了要请假,那么就要向班主任说明你的情况并作出申请,然后班主任同意了即可请假离校。其中学生和班主任是依赖关系,你一个学生总不能向你的好基友请假吧。所谓的依赖方法就是请假的程序,向班主任申请,班主任同意,离校…
切换回代码的角度,我要形成可执行程序,我要依赖源文件。依赖方法就是我接下来要怎么做,其中上图的依赖方法可以看到,我是要用gcc工具来编译形成可执行程序。
如何编写一个makefile文件呢?
我现在写好了一个伪目标makefile文件如下:
上面的test没有用PHONY修饰,那么只要vim里面的代码没有任何改动,那么make命令就不会执行。如下:
但是用了PHONY修饰的指令,会一直执行。如下:
我把test也加上PHONY修饰。
执行make命令如下:
那么g++是怎么知道文件到底更没更新呢?先来认识一下文件的三个时间
其中make命令就是根据依赖的文件与目标文件形成时间的先后来判断是否需要更新。
首先我们看到上面的makefile的文件内容是直接生成可执行程序的,但是可执行程序是怎么来的呢?同样的也是经过程序的翻译才能得到的可执行程序。所以根据刚才所说的,我们可以知道实际的makefile内容应当是这样的:
一句话总结,你可以理解为是数据结构中的栈结构,也就是先进后出。
下面来看两个代码
其中代码的不同就是少了一个换行符,在语言层面,换行符其实就相当于行缓冲。
第一个代码因为有了行缓冲,所以printf里面的内容直接被刷新。所以是先打印内容,再等待2s。
第二个代码因为没有行缓冲,所以printf里面的内容没有直接被刷新,而是先等待2s,然后再从缓冲区里面打印出来。
利用上面的缓冲区概念,我们可以利用这个特性,做出一个倒计时的小程序。
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 }
效果可以自行运行代码查看,很有趣的小程序。