程序翻译先后进行了预处理、编译、汇编和链接。
一般我们直接翻译(c语言)程序:gcc test.c -o test.o 【-o +文件名称 指名形成文件的名称】
(翻译test.c形成可执行程序文件test.o)现在可以细分为以下几个步骤:
步骤 | 大概过程 | 停止条件 | 目标文件后缀 |
---|---|---|---|
预处理 | 头文件展开,去注释,宏替换,条件编译等等—预处理后代码还是c语言 | -E | -i |
编译 | 把c语言变成汇编语言 | -S | -s |
汇编 | 把汇编语言转化为二进制 | -c(小写) | -o |
链接 | 编译器只是编译我们写的代码,还得把我们的代码和库里的代码链接形成可执行程序(你的代码+库) |
记忆:ESc—键盘左上角退出键 对应文件:iso一般指国际标准化组织/感光度/ 镜像文件
gcc -E test.c -o test.i ->预处理test.c文件,-E含义:从现在开始,进行程序的翻译,做完预处理就停下
-o:指明形成的临时文件名称(.i)【把程序翻译的文件保存在临时文件里,不会显性在Linux中显示出来】
我原来的代码是这样子的
如果不带-o 目标文件直接编译的话会这样
编译的内容直接在系统中显示出来;但如果加了-o 目标文件的话,会生成个目标(临时)文件test.i,然后会把这些内容储存在目标文件里
我们可以vim查看
发现内容是跟显示在系统上是一样的!所以我们在翻译程序的时候,最好指定生成目标文件!
gcc -S test.i -o test.s -S含义:从现在开始,进行程序的翻译,做完编译就停下
-S对应的目标文件后缀为.s
生成后我们vim查看test.s 可以看到现在就不是c语言了
**gcc -c test.s -o test.o ** -c含义:从现在开始,进行程序的翻译,做完汇编就停下
-c对应的目标文件后缀为.o
我们可以查看—发现更看不懂了
我们也可以通过od指令查看,发现的确是翻译成了二进制,但这个二进制目标文件不能被执行
gcc test.o 后面不带指定文件则默认生成a.out;带目标文件则生成目标文件
最后我们跑起来
,对于程序翻译的复习就到这。
我们实现的c程序里,使用了printf等等函数,头文件stdio.h也只是包含了声明,那具体实现是在哪里呢?系统把这些函数的实现都放在名为:libxxx.so.6的库文件中去,当实现函数时,就去这个库文件里面找,而这就是链接的作用。
函数库一般分为静态库和动态库。
静态库指当编译链接时,系统把库文件里的代码加入到可执行文件中,因此生成的可执行文件较大,但程序运行的时候就不需要库文件了。其后缀一般为.a
而动态库恰恰相反,当运行起来,函数实现时,系统会把链接文件加载到库,这样就节省了系统开销。
做个形象的比喻:当你需要干饭时,静态库就是在家自己给自己做菜然后吃饭-此时就不需要外面的饭菜提供;动态库就是去外面的饭店吃饭,省出家里做菜的空间。
动态库 | Linux系统下库的命名(libxxx.so)-xxx为库的名称 | windows系统下命名 | 优缺点 |
---|---|---|---|
——— | 后缀为.so | 后缀为.dll | 形成的可执行程序小,节省资源-磁盘,内存,网络 |
静态库 | libxxx.a -xxx为库的名称 | ||
——— | 后缀为.a | 后缀为.lib | 形成的可执行程序大;不受库升级或者被删除的影响 |
现在我们可以试着去验证,我创建了test.c文件并在里面写好了c语言代码
然后我们编译它
通过file可以看到它是动态链接的-那么我们知道在Linux系统下默认允许程序是链接的动态库!
可以看到这是动态库,名称为libc.so.6其中c为c库
所以我们区分库要去掉前缀lib 去掉后缀so,剩下的就是库名称:c
我们还可以通过查看知晓动态库
我们还可以看看静态库:
可以看到链接静态库的可执行程序大小比链接动态库的大了十倍!
同样的我们可以查看知晓静态库
一般而言链接动态库时只能找动态库,不能找静态库,由于Linux系统下大部分指令都需要用到动态库,所以系统会自带动态库,而静态库需要我们自己安装。
下面是c语言静态库安装指令
sudo yum install -y glibc-static
下面是c++静态库安装指令
sudo yum install -y libstdc+±static
好啦gcc和g++的使用就讲到这。
make是一个命令,makefile是一个文件。
1.make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命
令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一
种在工程方面的编译方法。
2.makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编
译,极大的提高了软件开发的效率。
我们先创建一个文件mycode.c,在里面写好程序
再创建一个文件,文件名为Makefile(makefile也可);然后在里面写
Makefile文件的第一行要写依赖关系【儿子:爸爸 —即儿子依赖爸爸—即前者依赖后者】
第二行要以table键开头!
第二行写依赖方法【儿子为什么要依赖爸爸呢?—以这个代码为例编译mycode.c形成mycode—mycode的产生得由mycode.c来决定!】
然后我们出去make一下,发现执行了依赖条件的代码,然后我们去运行可执行程序发现可以运行
然后我们再加点东西进去
首先我们来看这个**clean:**这里也是一个依赖关系,但clean冒号后面没有接东西说明clean不依赖任何文件或程序。
然后它的依赖方法是删除mycode
我们出去执行,发现它确实能执行
但这次clean前面有个**.PHONY:clean** 这里又与上面mycode的依赖关系和方法有什么区别呢?
我们连续make,发现后面的都提示mycode已经是最新的了,不需要再编译了
但是我们连续make clean,就算mycode已经被删除了,还是能执行make clean,那是为什么呢?
别急,我们先来看这个—mycode.c的时间
Access | 文件访问的时间—新版的系统对更新频率做了修正,访问过一段时间或者访问了多次才会改变 |
---|---|
Modify | 内容修改时间—内容改变时间随之改变 |
Change | 文件属性修改时间—文件属性改变时间随之改变(内容修改了文件属性也会变-比如文件大小等等) |
然后我们改变一下mycode.c的内容
我们发现时间变了。
是否能make 是根据依赖关系两边的文件时间改变而决定的。
我们知道编译文件产生目标文件,所以编译文件修改时间在目标文件修改时间之前。比如编译mycode.c产生mycode,那么mycode.c的修改时间肯定在mycode的修改时间之前,如果是这样那么Makefile文件识别到这样就不需要在make了。
如果make之后我们去修改了mycode.c,那么mycode.c文件的修改时间就在mycode修改时间之后。—编译文件修改时间在目标文件修改时间之后,这合理吗?所以Makefile要make一下。
我们可以实验去证明。另外我们知道。touch不仅可以创建文件,还可以刷新文件时间。
我们先make 文件mycode.c直到不能再make,然后我们touch刷新时间,之后我们发现又能make了!
峰回路转,我们得出结论,.PHONY的作用是不再用时间作为指标来执行make!
.PHONY:,冒号后面的是伪目标(或伪文件)—不再用时间作为指标来执行make!
所以我们可以试试在前面加.PHONY
我们出去make发现确实如此!
另:makefile默认情况下 从上到下自动执行第一个目标文件,这就是为什么后面的clean要我们另外执行的原因。
实际上形成目标文件mycode是要通过前面说到的对mycode.c进行预处理形成mycode.i,编译形成mycode.s,汇编形成mycode.o,最后链接才产生可执行文件mycode。所以依赖关系并不是按照从上到下,而是按照推导规则来的!
我们可以打乱顺序
然后去make
我们发现make出来的顺序并不是按照在Makefile文件上从上到下那样的顺序,而是按照编译的逻辑顺序来的!
好啦对于make和makefile的介绍就到这里了。这里的各自推导规则有点难以理解,学习这里的小伙伴要有耐心噢。
最后我们来总总结一下这篇文章,
我们用翻译程序的过程来引出对g++/gcc的使用,翻译程序的过程有:预处理—编译—汇编—链接;前三者的停止条件为:ESc,对于产生的文件后缀为:iso;链接有链接动态库和静态库,linux下动态库为libxxx.so(后缀为.so,xxx为库的名称),静态库为libxxx.a(.a为后缀)
Windows下动态库后缀为.dll,静态库后缀为.lib ;
然后到make和makefile,makefile第一行是依赖关系,第二行是依赖方法,要以table键为起始;默认自动从上到下make第一个目标文件;依赖关系按照产生目标文件的程序逻辑走;.PHONY:伪目标 这个目标不以时间为标准来make;
看到这里的观众老爷们不妨点点观众点赞收藏走一波,你们的支持是我更新的不懈动力~~~