目录
一、gcc/g++编译器
1.程序的翻译过程
(1)预处理
(2)编译
(3)汇编
(4)链接
2.动态库与静态库
(1)动态链接和静态链接
(2)动态链接和静态链接的优缺点
(3)动态链接与静态链接的存储占用对比
(3)动态库与静态库
3.gcc与g++
二、linux项目自动化构建工具(make/makefile)
1.含义
2.make和makefile
3.make和makefile的使用
4.makefile原理
(1)makefile使用
(2)make命令执行逻辑
(3)“.PHONY”伪目标
(4)makefile推导规则
一个程序的翻译过程可以分为四步:预处理、编译、汇编、链接
在预处理中,程序会执行“头文件展开、去注释、宏替换、条件编译”等工作,为了更加直观的显示,假设我们现在有test.c文件,里面包含下图的程序:
现在我们执行“gcc -E test.c -o test.i”命令。该命令会将对应test.c文件执行完预处理后就结束,并将处理好后的文本内容放入test.i文件中
打开test.i后,前面的内容大部分人都看不懂,但是没有关系。我们直接翻到该文件的最后,此时就会看到以下内容:
与上图我们写的代码相比较,就可以看到,注释,条件编译和宏之类的内容就已经不见了,此时还留着的就是和代码强相关的内容
编译简单来讲,就是将预处理过后的“纯C语言”转化成汇编语言。此时我们输入“gcc -S test.c -o test.s”命令,该命令会在文件执行完汇编后结束并将结果放入test.s文件:
虽然上面的内容我们看不懂,但依然可以看出来这是汇编语言
对于这一步有些可能会感到奇怪,既然c转汇编在编译阶段已经完成了,那汇编要干嘛呢?我们要知道,汇编语言并不是最原生的语言,在计算机底层,是以0,1信号来执行的。而汇编这一步,就是将汇编语言转成二进制机器语言,将汇编文件转换成二进制目标文件
我们输入“gcc -c tst.c -o test.o”命令,就会在文件执行完汇编后结束,并将结果放入test.o文件中:
此时显示的是乱码,我们是看不懂的。但我们可以执行“od test.o”命令,以二进制的形式查看:
虽然此时我们已经有了二进制目标文件,但该文件我们此时依然无法执行。因为虽然我们写了这份代码,但代码中调用的c标准库中的函数还没有实现。而链接就是将我们写好的代码与c标准库中对应的代码合起来,使得我们调用的函数可用。
此时我们直接执行“gcc test.c”命令,就会直接帮我们生成对应的可执行程序
要注意的是,预处理用的命令是“-E”,生成的文件后缀是“.i”;编译用的命令是“S”,生成的文件后缀是“.s”;汇编用的命令是“-c”,生成的文件后缀是“.o”
在了解动态库和静态库之前,我们先来了解动态链接和静态链接。为了更容易理解,我们在这里举一个例子。
我们假设现在有一所学校。然后你现在作为一名住宿生,放假期间都是呆在宿舍里面。然后学校里面有一个食堂。在平时,我们呆在宿舍里面可以做很多事情,比如学习,睡觉等。如下图:
但是当你学习或者娱乐一段时间后,觉得饿了,想要吃饭,但是宿舍里面没有饭可以吃,因此我们就必须离开宿舍沿着一定的路线来到食堂吃饭。吃完饭后再回到宿舍做自己的事。如下图:
而这里面的宿舍就可以看做一个程序;食堂则可以看成是一个库。学习,睡觉等活动就是这个程序需要完成的工作。而前往食堂就是在进行库函数跳转:
学习和睡觉这些活动程序可以依赖自身的代码完成,但是吃饭却不行。因此此时程序就去到库里面执行库里面的代码,在库里面执行完后再返回。
但是我们怎么知道食堂的位置在哪里呢?当我们入学时,都会有老师或者引导人员告诉你食堂的位置,这些老师和引导人员就可以看成是编译器里面的链接器,将库的位置告诉程序,程序到对应的位置去找。而程序从自己所处的位置到库里面执行代码的过程就是“动态链接”
然后有一天,学校发通知说食堂需要装修更新,暂时停运了。但是为了解决学生的吃饭问题,于是学校给每个宿舍都发了食堂里面的炒锅和菜,现在我们要吃饭时就不需要去食堂了,而是直接在宿舍自己做就行了。
现在我们将宿舍看成我们的程序,就可以认为,程序在执行需要进行库函数跳转的操作时,因为库中对应的代码已经被包含在程序里面了,于是无需再进行跳转,在自己内部就可以完成。可以认为,这种在链接的时候,不是与库产生关联,而是将我的程序内部需要的库里面的方法、代码拷贝一份放到我的程序里面的行为,就叫做“静态链接”
1.动态链接
优点:
动态链接是从库里面找方法,这就意味着程序本身并不包含库,也就说明动态链接的程序存储占用会比较小。在加载到内存时体积小的程序也会比较快
缺点:
动态链接需要到库里面去寻找方法,这就导致如果库因为某些原因无法使用,比如上文中说的食堂需要装修就好比是库需要更新换代了,此时程序就无法运行了。虽然更新后也许会更好用,但在更新时我们是无法使用的
2.静态链接
优点:
静态链接的优点在于程序运行不会受库的影响。就好比在你能够在宿舍自己做饭的情况下,食堂无论发生什么都影响不到你,无论是好是坏。静态链接也一样,库的情况不会影响到程序的运行
缺点:
静态链接实现的方法就是要将程序需要的资源放到程序中,同时程序内部解决一个需要调用库里面的代码的问题时,需要拷贝一份内部的解决方法到对应的位置,这就会不可避免的导致程序存储占用增加。并且程序在运行时需要加载到内存里,程序体积大就会导致加载时间长
为了更加直观的看到动态链接和静态链接带来的存储占用变化,假设此时我们有以下一个程序:
我们分别以动态链接的方式和静态链接的方式生成可执行程序,看看它们的体积大小。
动态链接:
首先我们执行“gcc file.c -o myfile”命令,在linux中,默认生成的可执行程序都是动态链接的
可以看到,此时就生成了一个“myfile”文件。该文件的体积是8360byte,即8k左右
如果不确定该程序是动态链接的,可以执行“file myfile”命令,查看该程序的详细信息:
在这里显示的内容我们可以看到“dynamically linked”信息,意思就是动态链接
我们也可以输入“ldd myfile”命令查看该程序链接的是哪个库:
静态链接:
如果想以静态链接的方式生成一个程序,就需要加上“-static”。如我们现在输入“gcc file.c -o my_file -static”:
可以看到生成了一个“my_file”文件,该文件的体积为861288byte,大概800k左右,体积是动态链接的文件的100倍。而这仅仅只是一个简单的打印程序相差的倍数,如果加上其他的功能,体积相差可能会更大。这就是为什么在linux中默认使用动态链接。
1.含义
简单来讲,动态库就是程序库函数跳转后找到的库,静态库则是存在于程序内部的库
2.动态库与静态库的区分
动态库和静态库前缀都是由“libxxxx”组成的,区别在于后缀。动态库后缀是“.so”,静态库后缀则是“.a”。
而库的名字就是去掉前缀lib和后缀后的名字。如以下库:
去掉前缀lib和后缀“.so.6”和就只剩下c,这个库其实就是c标准库
3.系统中的动态库与静态库
注意,千万不要随便删除系统中的c动态库。因为系统中的指令都是依赖与c动态库的,一旦删除,系统中的大部分指令就都无法使用
同时区别于静态库可能存在相同的几份拷贝,动态库可以看成是一个共享库,只要你用c写的程序能链接到动态库,那么你只需要有一份动态库即可,无需多份拷贝
(1)c静态库
一般而言,系统中会默认存在动态库,静态库则不一定有。如果你的系统中没有静态库又需要用到静态库,就要使用“yum install -y glibc-static”命令下载静态库。如果是普通用户就要在前面加上“sudo”
(2)c++静态库
既然C语言有静态库,同样的C++也有静态库,如果需要安装C++的静态库,输入“yum install -y libstdc++-static”命令即可。普通用户也需要带上“sudo”
gcc和g++是两个编译器。前面我们在编译程序时,写的都是c语言的代码,这些c程序就是需要使用gcc编译器。如果我们以后用的是C++语言来写程序,就需要使用g++编译器。使用方法也很简单,编译时将gcc改为g++即可
makefile的工具使用起来非常复杂,需要及其庞大的篇幅,在这里我们并不会详细介绍,而是讲解一些makefile的基本使用
在现在我们写的程序都是小型程序,基本只有2个或3个文件。但是在实际中我们会遇到一个大型程序中有成百上千乃至更多文件的情况。在这种情况下,就会有文件先编译后编译和其他更加复杂的操作需求。由我们一个个去执行效率是很低的,为此就有了makefile这一“自动化编译工具”
makefile是一个文件,在这个文件中存有我们程序编译执行的相关逻辑等内容,可以支持工程的自动化编译,提高软件开发的效率。
make则是一个命令工具,用于解释makefile中的指令。
假设现在我们有以下一个file.c程序:
makefile在系统中并不存在,使用时需要我们自行创建。此时我们自行创建一个makefile文件:
然后我们在makefile文件中写入以下内容:
这条命令想必大家都很熟悉,就是用来形成可执行程序的。我们将这些内容保存后退出,然后执行make命令:
可以看到在我们没有执行“gcc file.c -o myfile”命令的情况下,生成了的对应的myfile可执行程序。这是因为使用make命令后就会将makefile文件中的命令逐个执行,而makefile中是写了对应的执行命令的
在makefile中要包含两个东西,“依赖关系”和“依赖方法”。
举个例子,假如你现在到月底了,没有生活费了,那需要向家里要生活费。此时你打电话给你爸,告诉他你没生活费了,要你爸发钱给你。那此时为什么你爸会发钱给你呢?那是因为你是你爸的儿子,这就是“依赖关系”。然后你需要的是你爸把钱发给你解决你没有生活费的问题,你爸把钱发给你这个动作就是“依赖方法”
在makefile中,首先要包含“依赖关系”。在makefile文件中的第一行就是依赖关系。
1.用make生成一份可执行程序
例如我们现在有以下程序,我们想用make命令让这个程序生成一份“可执行程序myfile”。
为此,我们就要在makefile文件的第一行写上“依赖关系”,即“myfile:file.c”:
左边的myfile就是我们要形成的myfile目标文件,右边的file.c则是我们的依赖文件列表。“:”代表了依赖关系。这行代码表明的就是依赖关系——myfile文件的形成依赖与file.c
新起一行,也就是第二行就是我们的依赖方法。此处我们是要形成一份可执行程序,因此我们就在第二行写上对应的命令:
注意:新起的一行,即依赖方法的开头必须用按下“Tab”键的内容,不能输入四个空格来模拟“Tab”键的内容来开头:
此时我们就有了一个简单的makefile文件。此时我们直接使用make键就可以执行对应的命令:
2.用make删除一个可执行程序
现在我们的makefile文件中已经有了生成可执行程序的代码,但是还缺少删除该可执行程序的命令。此时我们就可以在makefile文件中写上“clean”,并带上对应的依赖方法来清理,:
可以看到,我们这里的“clean:”后面什么都没有写。这就表明依赖关系中的右侧是可以什么都不写的,表明该目标文件不依赖于任何文件
我们通常会对clean用“.PHONY:”来修饰,表明clean是一个伪目标:
现在makefile中的清理文件命令就写好了。现在如果我们想生成可执行程序就使用“make”,想清理文件就使用“make clean”:
对于上述情况,现在可能有人会疑惑为什么执行make命令只会生成可执行程序,不会执行clean呢?原因就在于make命令在默认情况下由上而下执行,并只形成一个目标文件。同时第一个目标文件可以不带目标名。
这就是说如果我们愿意,也可以用“make myfile”的形式执行第一个目标文件:
但每次make命令都只会形成一个目标文件
1.“.PHONY”伪目标的意义
伪目标的意义就在于使一个目标总是被执行。这句话不是很好理解,我们现在举一个例子来说明。
假设现在我们执行make命令生成一份可执行程序:
在有这份可执行程序且源文件“file.c”并没有被修改的情况下,我们再次执行make命令:
可以看到,此时系统不让我们执行该命令,原因是生成的可执行程序已经是最新的。那么现在我们打开源文件“file.c”进行修改:
当我们修改完后就会发现再次执行可以执行,但是我们依然无法连续多次执行make命令。
此时我们进入“makefile”文件,加上“.PHONY:myfile”命令:
退出来后我们再次执行“make”命令:
可以看到,此时我们就可以在不修改文件的情况下多次执行make命令。这就是“.PHONY:”伪目标的作用。使一个目标可以总是被执行
2.“.PHONY”实现原理
我们都知道文件属性中有三个时间,分别是“Access”、“Modify”、“Change”。其中的“Access”是文件访问时间;“Modify”代表文件内容被修改的时间;“Change”代表属性被修改的时间。
但是要注意:“Access”时间并不会访问一次就更改一次,因为这种方式带来的时间修改次数太高,对系统来讲是负担。为此,“Access”时间现在通常设定为访问一定次数或一定时间后才修改。
现在有了文件属性中的这三个时间概念后我们就能更好的理解为什么在没有“.PHONY”修饰时系统可以判断无需再执行make命令。
原因就在于“先有源文件,才有可执行程序”。这就意味着源文件的“Modify”时间总是比可执行程序的“Modify”时间早。而一旦源文件的“Modify”时间比可执行程序的“Modify”时间晚,就代表源文件被修改,可执行程序需要重新编译。而“.PHONY”的作用就是让系统忽视这一时间判断机制,使得每次执行make命令时都重新编译
当然,在一般情况下我们不会对makefile文件中的形成可执行程序命令加上“.PHONY”。因为在实际中的一些中大型程序编译一次就可能需要几十分钟,几小时甚至更久。此时如果对没有修改的源文件重新编译就会浪费很多时间
如果我们在没有写“.PHONY”并且不想修改源文件的情况下想重新编译,就可以使用“touch 文件名”命令。该命令如果带一个不存在的文件名则是生成对应文件名的文件;如果带一个已经存在的文件名就会更新该文件的时间:
现在假设makefile文件中有以下内容:
我们都知道可执行程序形成是“预处理、编译、汇编、链接”四个过程。我们现在将这四个过程一一写出并形成对应的文件:
上文说了makefile中默认执行第一行命令且只形成一个目标文件。但是对应的目标文件的依赖文件不存在,于是会向下寻找:
直到找到所有目标文件需要的依赖文件。进而就会像上图那样逆序执行。当然,要生成一个可执行程序无需这样写,这里只是演示一下makefile的执行逻辑,实际中直接“myfile:yfile.c”即可。