【Linux】linux基础开发工具(二) 编译器gcc/g++、动静态库感性认识、自动化构建-make/Makefile

发布于:2025-08-01 ⋅ 阅读:(13) ⋅ 点赞:(0)


一、gcc/g++介绍

我们之前介绍了编辑器vim,可以让我们在linux上linux系统里都默认安装了gcc和g++。gcc只能编译C语言,g++主要编译C++,也可以编译C语言。

gcc编译文件后默认会生成可执行文件a,out,示例如下:

在这里插入图片描述

但是gcc编译文件的最佳实践不是生成默认的a.out,而是用-o选项,生成我们自己命名的可执行文件。

在这里插入图片描述

当我们需要编译多个文件生成一个可执行程序时,可以用下面的格式,反正记住-o选项后面紧跟可执行文件名就不会错了。

在这里插入图片描述

二、gcc编译选项

在这里插入图片描述

因为gcc支持分步骤编译代码,所以我们可以通过实践来看各阶段编译器做了什么,这里就需要我们通过gcc的不同的编译指令来查看。gcc编译文件格式大致如上,小编来挨个介绍:

预处理

(进⾏宏替换/去注释/条件编译/头⽂件展开等)

在这里插入图片描述

-E选项是指从现在开始gcc开启翻译工作,当把预处理进行完毕后就停下来,所以hello.i文件里的内容就是hello.c文件经过预处理后的结果,下面我们通过vim来分析预处理阶段编译器会完成的工作:

在这里插入图片描述

头文件展开就是指gcc把已经在系统里安装好了的头文件内容拷贝一份到指定文件中,不如stdio.h就在系统的如下路径:

在这里插入图片描述

条件编译本质就是通过宏的定义与否对代码进行裁剪。操作系统的内核裁剪就可以通过条件编译来实现。

编译

(将C语言翻译为汇编语言)

在这里插入图片描述

-S选项是指从现在开始gcc开启编译工作,当把编译进行完毕后就停下来

汇编

(将汇编语言翻译为机器可识别二进制代码)

在这里插入图片描述

-c选项是指从现在开始gcc开启汇编工作,当把汇编进行完毕后就停下来
这里的hello.o叫做可重定位目标二进制文件,因为它还无法被直接执行,因为头文件只包了相关库函数的声明(如printf),库函数是实现在对应的库里,所以经过汇编后的文件还需要在链接期间和相关库进行链接,才能形成可执行文件。
所以在链接之前编译器只会翻译我们自己写的代码。

链接

(⽣成可执⾏⽂件或库⽂件)

在这里插入图片描述

链接指令不需要加额外的选项,直接-o就行了。
我们前面已经初步介绍了.o文件不可执行的原因,因为还需要链接这个步骤,那么接下来小编就来介绍链接到底做了什么让.o文件变成了可执行文件。首先.o文件里除了库函数以外的文件都已经被翻译成了二进制代码,而库函数只是在头文件里有声明,没有具体实现,所以在.o文件里库函数调用那里是没有填要调用函数的地址的,只有在链接期间.o文件和库函数链接上了库函数那里才能填上具体要调用库函数在库里的地址,因为库是一组预先编译好的代码集合,所以填上地址后当代码执行到对应库函数时会跳转到库里对应的地址实现库函数然后再返回继续执行后续的代码。所以这里也可以解释为什么.o文件叫做可重定位二进制文件。

三个细节

1、有了上面的认识,所以我们以后要编写代码,必须要提前下载好头文件和库,linux下系统会自动帮我们下好,windows下我们在下载IDE时需要勾选相关选项下载。
2、库的出现是为了让程序员直接使用,提高开发效率。C++11 C++17 之类的更新本质就是在库里添加了新的方法或者改变了编译器的语法规则。
3、库的分类如下图所示,库的名字一般是去掉前缀lib,去掉后缀.so或.a后,剩下的就是库的名字。

在这里插入图片描述

三、动静态库感性认识

静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。其后缀名⼀般为“.a”
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。

动静态库和动静态链接之间的联系:库是链接的对象,无论是静态库还是动态库,都是在链接过程中为程序提供所需的代码和功能,链接是将库与应用程序结合起来的操作。

前面我们认识到链接分为静态链接和动态链接,我们看下面这个案例可以知道编译器默认采用动态链接的方式生成可执行程序。我们用file来查看编译生成的可执行文件的详细属性。
ldd可用来查看一个可执行程序依赖的库。

在这里插入图片描述

如果我们想让编译器采用静态链接的方式生成可执行程序,需要加一个-static选项。

在这里插入图片描述

但是实践操作系统会提示编译失败,因为我们的系统里没有安装c/c++的静态库,在cnetos下安装静态库的指令如下:
sudo yuminstall -y glibc-static
sudo yum install -y libstdc++-static

动静态库的优缺点

动态链接:(类比网吧)
需要的方法需要跳转到库里去执行
优点:节省资源
缺点:一但丢失,程序无法直接运行
稍微慢一点

静态链接:(类比一人一台电脑)
把你想要的方法拷贝到你的可执行程序中。
优点:不依赖任何库,程序自己能独立运行
缺点:体积大,占据资源多(占据磁盘空间,内存空间)
加载速度慢

四、自动化构建-make/Makefile

背景知识

1、会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒。
2、⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作。
3、makefile带来的好处就是⸺“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全⾃动编译,极⼤的提⾼了软件开发的效率。
4、make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可⻅,makefile都成为了⼀种在⼯程⽅⾯的编译⽅法。
5、make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。

初步上手Makefile

(注释Makefile里的内容用#)
首先我们要明确make是linux系统内置的命令,Makefile是一个需要我们自己创建的文件。我们先来感受一下make的便捷:

在这里插入图片描述

这上面的操作要建立在我们自己创建出makefile文件的基础上,下面小编来介绍如何创建makefile(makefile本质就是编写依赖关系和依赖方法):

在这里插入图片描述

我们看上面的样例,前两行就是编译源文件需要在makefile写的依赖方法。
后三行表示移除生成的可执行文件(项目清理),.PHONY表示跟在它后面的被修饰的目标是一个伪目标,伪目标的特点是总是会被执行,而且依赖文件列表为空。那么什么是不会总是被执行呢,比如我们的code.exe就不是伪目标,当它被make构建出来后,如果源代码code.c没更新make编译器就不会再执行把code.c编一遍,这样做是为了让编译工作高效一些,因为重复编译会浪费资源。所以这里的最佳实践是可执行程序不需要被.PHONY修饰,clean需要被.PHONY修饰。因为清理工作效率很高,而且清理需要清理彻底。
这里又有一个问题,make是怎么知道code.c是否被编译过呢?这里就涉及文件的修改时间,我们来看下面:

在这里插入图片描述

一般只要文件内容改变,文件属性会跟着改变,因为涉及文件大小的变化,即便文件大小没变,modify本身也是文件属性,修改文件内容modify一定会被更改,所以change会随着modify的改变而改变。如果我们修改文件权限的话就只有change改变。
编译器判断源文件是否被编译过就是比较源文件和它的可执行文件的文件内容修改时间哪个更新,如果源文件更新编译器就会编译源文件,如果可执行文件更新说明源文件已经被编译过了,而且源文件没有被修改,那么就不会重新编译。因为一开始只有源文件,所以两者的modify时间一定有先有后,不可能重叠。

在这里插入图片描述

所以被.PHONY修饰过的文件总是被执行的原理就是让make忽略源文件和可执行文件之间modify时间的对比,只要make就会执行依赖方法。

make指令默认只会形成一个目标文件,就是make在makefile里从上到下扫描遇到的第一个目标文件,如果我们要指定make其他文件就需要在make指令后面跟上其他目标文件的文件名。我们在工程中的最佳实践是把要生成的可执行文件放前面,clean放后面。

makefile的推导过程

make根据依赖关系对makefile进行正向解析,解析过程中将依赖方法依次入栈,解析到末尾依次将依赖方法出栈并执行依赖方法,就可完成单次可执行程序的翻译。

在这里插入图片描述

但是我们实际上是不会把编译过程拆分的这么细的,我们平时的最佳实践是把多个源文件都翻译成.o文件,然后将多个.o文件与库做链接生成最后的可执行程序。我们之前的例子只有一个源文件,所以直接将源文件翻译成可执行程序也没有任何问题。
补充:gcc的-S -c选项如果后面不跟-o指定文件的话会默认生成同名的.s和.o文件,如code.c会生成code.o。

在这里插入图片描述

makefile语法

前面我们编写makefile是直接将方法和文件写在makefile里,不过在实际工程中喜欢将它们设定义为变量,所以就会涉及一系列语法,小编来一一介绍。

  • @

(关闭指令回显)
我们前面在make 和 make clean之后系统会把具体指令显示出来,如果我们不想让他显示就在makefile的指令前面加上@

在这里插入图片描述
在这里插入图片描述

  • $

(取这个符号后面的括号里变量的内容)

在这里插入图片描述

有了这个符号我们在makefile里编写指令就不用再出现具体的文件和指令了,我们只用写出通用的makefile,只要想改变某个变量的内容一键替换就行了,类似于宏和全局变量。注意每个$()之间要用空格隔开。示例如下:

在这里插入图片描述

  • $@、$^

($@代表:左边的内容,$^代表:右边所有内容)
$^可以包括:右边所有文件,只是这里只有一个文件。

在这里插入图片描述

第一次优化:
我们之前介绍说先把源文件翻译成目标文件然后再整体链接成可执行文件,所以我们这里也按这个思路优化一下makefile:

在这里插入图片描述

  • wildcard函数

(获取当前目录下所有以.c结尾的源文件)
如果我们有很多源文件时,SRC=后面要把源文件挨个写出来,很麻烦,所以这里引入wildcard函数(touch file{1…10}.c 表示一次性创建file1.c到file10.c 10个文件):

在这里插入图片描述
第二次优化:

我们已经可以把一次性所有,c文件获取到全部翻译成.o文件了,可是我们目前还无法一次性获取所有.o文件,那么我们要把所有.o文件链接为可执行程序就要挨个将.o文件写一遍吗?并非如此,下面这条指令可以简化我们的工作:

在这里插入图片描述

它可以将SRC里的所有.c 文件替换为.o文件然后将所有.o文件放到OBJ里。

在这里插入图片描述
一步登天:
在这里插入图片描述

这是我们要构建多个文件时makefile的最终形态,我们先提前科普几个概念:
$(OBJ): $(SRC):
$(OBJ) 中每个.o 文件的生成,都依赖于 $(SRC) 中所有 .c 文件,大部分场景这种粗暴的依赖方式都用不到,而链接时是$(BIN): $(OBJ),需要由多个$(OBJ)里的目标文件链接成一个在$(BIN)里的可执行程序,所以链接时符合这个依赖关系。
%.o:%.c
任何 .o 文件的生成,都依赖于同名的 .c 文件,这个就正好符合我们后面会介绍的将源文件翻译成目标文件的场景。
$^
将:右边的所有文件一次性交给gcc来编译。
$<
将:右边的所有文件一个一个的依次交给gcc来编译,gcc一次只会编译一个文件。

在这里插入图片描述

因为编译源文件到目标文件需要一个一个文件单独编译,所以不能用$(BIN): $(OBJ)
依赖关系和$^,它们是用来一次性make多个文件的。最后链接操作符合我们上面介绍的规则。依赖方法执行完后还可以echo一句提示来让用户知道make具体做了哪些工作。

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到