Linux《基础开发工具(中)》

在之前的Linux《基础开发工具(上)》当中已经了解了Linux当中到的两大基础的开发工具yum与vim;了解了在Linux当中如何进行软件的下载以及实现的基本原理、知道了编辑器vim的基本使用方式,那么接下来在本篇当中将接下去继续来了解另外的两大基础的开发工具gcc/g++和自动化构建make/makefile,相信通过本篇的学习会使得你会有大的收获,一起加油吧!!!


1.gcc/g++

在之前的C语言的学习时我们就了解了程序由我们实现的代码到最终的可执行程序是要经过编译和链接两个大过程的,并且编译还可以细化分为预处理、编译、汇编三个过程。在之前的C语言的编译和链接以及深入理解预处理就了解了以上提到的过程基本的功能是什么,实现之后的结果是什么。但是之前我们在学习的过程当中只是将各个过程实现的功能大体的进行讲解,接下来我们就将在Linux当中验证之前我们了解的各个过程具体实现的结果是否和我们的预期是一样的。

1.1 编译链接

预处理

我们知道在预处理进行的是去注释、宏替换、条件编译、头文件展开等的工作,那么在Linux要得到一个文件只进行预处理之后的文件就需要用到gcc/g++编译选项的指令

注:gcc/g++都是编译器,其中gcc只能编译C语言,而g++既可以编译C语言也可以编译C++

gcc -E [原文件名] -o [生成.i的文件]

使用以上指令就可以将源目标的文件生成一份进行预处理之后的文件,在此一般会将该文件的后缀命名为.i,以上指令当中**-E就表示在gcc/g++执行的过程当中只要预处理执行完就停止** ,-o之后表明预处理之后要生成的文件的名称

例如以下示例:

以下实现了一个test1.c的文件,之后我们使用vim在里面编写了以下的代码

接下来就来使用以上我们学习的指令来使得gcc编译过程中只执行预处理之后就停止,生成的文件名为test1.i

执行了指令之后接下来就打开生成的test1.i,这时定位到main就可以看到我们代码当中的头文件#include<stdio.h>已经被展开,并且宏M也进行了替换;原先代码当中的注释也去掉了

编译

我们知道在编译过程当中主要就是将预处理之后的C/C++等的语言代码转换为汇编指令 ,在Linux当中要使得gcc/g++在执行过程当中到编译之后就停止需要使用以下的指令

gcc -S [源文件] -o [目标生成的.s文件]

以上指令的-S选项就可以使得执行完编译就停止,-o选项之后的文件名表明的是要生成的目标文件名,一般会将只执行到编译之后的文件名后缀命名为.s

例如以下示例:

以上我们已经将对应的test1.c使用gcc -E生成了对应的test1.i,那么接下来就继续将该test1.i使用gcc -S生成对应的test1.s

接下来查看生成的test1.s就可以看到此时该文件内已经将原来的C语言代码转化为了汇编代码

汇编

我们知道汇编主要实现的就是将编译之后生成的汇编代码生成机器码,也就是原先的代码当中变成了只含0、1的二进制码。在Linux当中要使得在gcc/g++执行时到汇编这一步就停止就需要使用以下的指令

gcc -c [源文件] -o [生成的目标.o文件]

以上的指令就当中**-c表示的是使用gcc/g++时到汇编这一步执行完就停止** ,-o之后的文件表示的是要生成的目标文件名 。一般将只执行到汇编的文件后缀命名为**.o**

例如以下示例:

以上我们已经使用gcc -S指令生成了对应的执行完编译之后的文件test1.s,那么接下来就继续使用gcc -c指令生成执行完汇编的文件test1.o

使用vim打开生成的test1.o文件之后就可以看到现在该文件内是一堆乱码,这就是因为此时该文件已经是一个二进制文件,直接打开是无法查看该文件的内容的

链接

在链接过程主要是将对应的.o文件和库文件进行链接

要将对应的原文件生成对应的可执行程序就需要使用以下的指令

以上我们就将之前在C语言了解的程序的编译过程在Linux

gcc [源文件名] -o [目标文件]

例如以下示例:

以上我们已经使用gcc -c 生成了执行汇编之后的文件,那么接下来继续将该文件使用gcc 形成test.out

运行产生的test.out

当中具体的演示了一遍,但其实以上的知识在之前我们就已经掌握了,在此只不过是在复习加深。其实在Linux的学习当中我们更要了解的是编译链接当中的链接,之前我们只是知道在汇编之后进行链接时会将此时的.o文件和其他的文件进行链接生成对应的可执行程序,但是在这个过程当中具体是如何实现的在此我们还无法进行详细的讲解,这是因为在该过程当中牵扯到了进程和文件 的相关知识,因此我们会在了解完进程和文件系统之后在动静态库和动静态链接再对链接进行详细的了解

1.2 几个小问题

在此你可能就会有疑惑了,为什么在将我们写的代码转化为可执行程序的过程当中要经历过编译之后再进行链接?

在我们实现一个大的工程项目时,不可避免地要解决不同用户的使用,就比如在一个网页的开发软件内就可能会有免费版和专业版的区别,那么此时在这两个版本当中就需要使用两份不同的代码吗?这样确实是可以的,不过一般是不会这样做,这是因为专业版当中相比免费的版本只是添加了一些功能,这时如果使用不同的底层代码就需要维护两份代码。在这种情况下条件编译的作用不就显示出来了吗,此时在免费版当中就将对应的专业版功能的代码进行条件编译,只要当用户是以专业版进入时才会将这份代码开放,这时在底层就只需要维护一份代码即可。

因此编译的作用简单来说就是减少语言的开发成本

接下来我们来了解什么是编译器的自举

我们知道gcc是用于编译C语言的代码,最后将其编译为机器语言。其实gcc底层的代码也是由C语言实现的,但是问题就来了语言的编译器和C语言那个先诞生呢?

其实是先诞生对应的编译器编译器的之后才产语言的,其实一开始最早的是使用纸袋打孔的方式来实现编程的,此时对应有孔无孔就分别表示1,0。该语言就是机器语言。

之后使用机器语言编写出了了汇编语言的编译器,我们知道编译器的作用可以来

就例如在C语言诞生之前先使用汇编语言产生C语言的的编译器可以编写软件,那么编译器不也是软件,因此就使用汇编语言写出了底层代码为汇编语言的编译器。

在此之后使用汇编语言编写出了C语言的编译器,在使用之后再使用C语言编写出底层代码为C语言的的编译器。

以上描述的过程当中有汇编写出汇编语言的编译器、由C语言写出C语言的编译器都是属于编译器自举。

在此还有问题就是为什么在C、C++当中都是要将将对应的程序代码转化为汇编语言再由汇编语言转化为机器语言?

这其实就是为了提高转化的效率,因为在C/C++诞生之前就已经形成了完善的汇编转化为机器语言的体系,所以在实现出C/C++之后只需要建立C/C++和汇编之间的转化关系即可;这相比要建立C/C++和机器语言之间的直接关系要容易的多,并且效率还更高。

1.3 动静态库和动静态链接

接下来在了解了gcc/g++的使用之后接下来来了解动静态库。

• 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此升成的文件比较大,但在运
行时也就不再需要库文件了。其后缀名一般为".a"
• 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由
运行时链接文件加载库,这样可以节省系统的开销。动态库⼀般后缀名为".so"

在此在Linux当中动态库一般是libXXXX.so的格式,其中XXXX是库的名称,静态库一般是XXXX.a的格式。在Windows当中动态库一般是XXXX.dll的格式,静态库一般是XXXX.lib的格式。

因此动态链接就是指在链接过程当中链接动态库,静态链接就是指在链接过程当中在链接的是静态库。

接下来我们就来看看Linux当中的C标准库,在此C标准库一般是保存在/lib64/libc.so.6 路径当中,接下来就使用ls指令来看看

这时就可以看到C标准库名称为c-2.17

在此要看一个可可执行程序的链接情况可以使用以下的指令

ldd [文件名]

例如以上生成的test.out使用ldd就可以看出该程序进行的动态链接 ,链接的动态库如下所示

在此也可以使用file来查看可执行程序链接的方式

这时我们要知道的是Linux当中默认的是采用动态链接的方式。

但是如果要让在链接时进行静态链接呢?

由于Linux当中默认是不存在动态库的,因此我们要使用静态链接之前要先执行以下的指令

yum install glibc-static libstdc++ -static -y

之后要进行静态链接只需要在使用gcc时最后带上-static选项即可

例如以下示例:

将test1.c按照静态链接的方式生成对应的可执行程序test2.out,这时就会发现静态链接形成的可执行程序相比原来动态链接的可执行程序文件大小要大许多。

以上我们就了解了动静态链接的一系列操作,但是现在我们还是未了解动态链接和静态链接有什么区别,但是现在我们又无法理性的从原理具体的讲解,那么接下来就来感性的理解,来通过一个故事来理解动静态链接。

在此你是XXX一中的一名学生,由于之前在初中时学习就比较好,你考上了市里的一中。从初中开始你就喜欢在学习之后玩玩电脑游戏来放松放松,但是到了高中问题就来了,在高中里要住在学校的宿舍里,这就没法玩电脑了,这就使你很苦恼了,你之后有电脑游戏的放松才能让学习的效率提高。此时你想到在你家附近的不是有一个表哥是一中高三的学生吗?去找这位学长来了解有什么解决的方法,到了表哥的家里之后,从表哥的口里得知了在学校的门口向东200米左右有一个网咖,很多的学长都是去这个网咖里打游戏的。因此在入学之前就把表哥说的网咖的地址记到脑子里了。不久之后你就进入到了一中当中,到了周末你感到身心疲惫,最近没了游戏的放松你感觉自己对学习都提不上尽了,此时你就想到之前表哥说的网咖,想着我们早上把数学和 英语作业写完去网咖玩两个小时的游戏,之后下午再把其他的作业写完。想玩你就快速的把相应的作业写完了,出了学校去网咖了打了两个小时的游戏,到了下午你写其他的作业的时候感觉自己的学习效率都提升了。之后的日子里,你都在周末的时候去网咖打几个小时的游戏,有了放松的时间你的学习快速的提高。

在以上的故事当中其实你就是可执行程序,你找的表哥就是编译器以及链接器 ,此时从表哥的口里得知网咖的地址就是进行动态的链接 ,之后在周末的时候你就相当于可执行程序加载到了内存当中 ,执行到了要去上网就想到网咖的地址,这就是调用之前进行动态链接的时候得到的库路径 ,之后通过网咖的地址找到网咖就相当于找到的对应的动态库 ,之后从网咖回到学校进行写作业就相当于动态库的调用完成继续进行程序的执行

接下来继续来看以上故事的续集。

随着越来越多的人知道了学校的附近有这么一个网咖,在周末的时候很多的学生都去这个网咖上网了,不久之后就被你们一中的校长知道了,毕竟不是所有人都是像你一样去网咖是为了放松,大多数去的人都沉迷在了游戏当中这就使得大多数学生的成绩大幅度的下降,校长想着是时候解决这个问题了,不久之后校长就告知附近的公安这家网咖非法的给未成年人上网,不久之后帽子叔叔就把这家网咖给拿下了。之后的一个周末当中和之前一样想去网咖放松一下发现网咖已经被封了,你就只好回宿舍了。之后的一段时间由于你无法在周末的时候上网来放松就让你没了学习的动力;学习成绩开始了下滑,从之前的年段前十掉到了百名开外。你的爸爸知道了你的成绩下滑之后就找你谈话,你就把你下滑的原因告诉了你的爸爸,你爸爸说这事放心我和你校长是好哥么,我去找他想想办法。之后你的爸爸就找了校长和他说了你学习成绩的下降是因为在周末的时候没法上网放松,平常的时候没法上网查一些资料,之后和校长说能不能开个特例让你在宿舍里安一台电脑,只要之后你再出现大的成绩下滑就把电脑撤了。这时你的爸爸就还找到了原来的学校附近被封的网咖,从这个网咖内购入了一台电脑,不久之后你的宿舍了就安上了。有了电脑的放松你的学习效率大大的提升,很快就回到了学校的年段前十。其他的学生看到你在宿舍里安了电脑,就和一样也纷纷在自己的宿舍了安装了电脑。

在以上的故事续集当中当原来的网咖被封之后你在自己的宿舍安装了电脑,这样上网不用去网咖里的操作其实就是静态链接 ,其特点就是直接将对应的库拷贝到可执行程序当中,这样在需要调用相应的库就不需要在调用的时候再去指定的路径当中调用对应的库,可以直接在可执行程序的内部就可以调用。

因此通过以上故事的讲述就可以看出动静态链接的优劣如下所示:
动态链接 :程序在调用相应的库的时候只需要通过路径即可找到,因此可执行程序的内部不需要进行库的拷贝,可执行程序的内存会比较小,但是动态库是共享的,所有库不能丢失否则就会出现所有依赖该动态库的程序在运行的时候出现错误。

静态链接 :优点是不依赖库,即使一个库出现丢失,原来进行过静态链接的可执行程序内部也不会出现运行的错误。但是静态链接需要将库拷贝到对应的可执行程序内,这就会使得空间的浪费,包括内存、磁盘、网络等。

2. 自动化构建-make/Makefile

2.1 背景

在了解make以及makefile之前先来了解make以及makefile的基本背景

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

2.2 make和makefile的使用方法

在以上我们了解了make以及makefile的重要性,那么接下来就来了解怎么使用make以及makefile内的文件内容该如何创建。

首先要知道的是要使用make 命令的前提是在当前的所处的目录下,含有名为makefile 或者Makefile的文件,因此在当中路径下我们就就先创建一个名为makefile的文件。

当前目录下创建了一个test1.c的文件,该文件内的内容如下所示:

之前我们使用将该文件编译为可执行程序是直接使用 gcc test1.c -o test1.out 来实现,而接下来我要使用的是先创建对应的makefile,之后再使用make命令来实现可执行程序的创建。

在此makefile内的内容就如下所示:

在此以上就是一个简单的makefile的内容,在此我们写的第一行就将要生成的文件依赖的源文件的依赖关系写出来,之后再写明了这两个依赖文件之间的依赖方法,其实也就是提供了源文件转化为目标文件的方式。

在此了依赖关系和依赖方法在现实生活当中可以映射到你和父母要生活费这件事上,只有你和父母的关系才能让你能执行向你的父母要生活费这个动作上,你的舍友就无法向你的父母索要。在此你和父母的关系就是依赖关系,你要钱的行为就是依赖方法,能执行依赖方法的前提是你和父母有依赖关系。

注:在此makefile当中编写依赖方法时要在依赖关系之后另起一行,并且之前还要空4个字符,若之前你已经将vim进行配置,那么在依赖关系之后按回车就会自动换行并且空对应的字符

编写了对应的makefile之后接下来回到之前的目录下,使用make命令就可以看到会将对应依赖关系的的依赖方法进行执行,并且还在当前的目录下生成了对应的目标文件test1.out

执行该文件就可以得到该可执行程序的输出内容

但其实以上我们编写的makefile是不完整的,一般在编写makefile的时候还要将项目清理工作也在makefile进行实现。

以上的maekfile在添加上清理功能之后如下所示:

以上就是添加上了clean之后的makefile,在此其实clean是一个伪目标文件,为什么接下来会讲解。

• 像clean这种,没有被第⼀个目标文件直接或间接关联,那么它后⾯所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令⸺"make clean",以此来清除所有的目标文件,以便重编译。
• 但是⼀般我们这种clean的目标文件,我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。

在此使用make时是会自顶向下扫描makefile文件,默认形成的是第一个目标文件 ,因此以上的使用make命令时会直接执行gcc test1.c -o test1.out

如果要想指定形成就需要使用 make targetname,就例如以上的make clean

接下来就回到原来的目录下,执行make clean命令

执行了之后再使用 ll 指令就会发现之前创建的test.out被删除了,并且连续多次使用make clean也可以执行

但是如果是多次使用make命令就只有当第一次是可以执行的,接下来的都会报错,这是为什么呢?

要解决该问题就需要了解到ACM时间,在此我们是使用stat来查看对应文件的ACM时间

stat [文件名]

以上使用了stat命令之后就可以看到三个时间的信息,在此Access表示的是之后一次访问文件的时间,Modify表示的是就是文件最近一次内容修改的时间,Change表示的就是文件的属性最后一次被修改的时间。

在此系统在判断是否要执行对应依赖关系的依赖方法时其实比较的就是对应源文件的Modify和目标文件的Modify时间,当目标文件的时间要比源文件的时间要靠后是才执行对应的依赖方法

那么为什么在操作系统当中要这样做,为什么不直接在用户每次使用对应的make命令时都将对应的依赖方法进行执行呢?

这其实是为了提高编译的效率,毕竟真实的情况不是像我们当前这样只是有一个源文件,真正的项目当中都是存在成百上千个源文件的,每一次编译都是需要耗费大量的时间的,因此当源文件的内容未出现修改时就不再进行编译,只有当原文件的M时间在目标文件之后才进行编译。

了解了以上的问题之后接下来就可以来解答之前提到的clean的伪目标文件 是什么了,在此伪目标文件就是在目标该文件之后的依赖方法不依据源文件和目标文件的M时间前后来判断是否执行,只要用户使用make命令执行之后就会执行该依赖方法

在以上我们了解makefile的基本编写方式,那么接下来进一步来理解makefile的推导原则

在以上我们了解了在编译当中是可以将编译具体划分为预处理、编译、链接三步的,那么接下来就将原来makefile内的一个依赖关系和依赖方法具体的展开

注:在此在makefile当中使用#可以进行注释

以上就实现了细节更加完整的makefile,接下来就回到之前的目录当中使用make命令

通过make命令输出的结果就可以看出其实在使用make指令时是将makefile内的依赖方法因此入栈,推导完毕依次出栈。

以上只是让我们了解make是会进行依赖关系的推导的,直到依赖文件是存在的。但是一般我们不会将makefile写成以上的形式,接下来写更加具通用性的makefile。

其实在真正的项目当中可能是会有多个.c文件的,在此一般都是将这些.c文件编译为.o文件,之后再和其他的第三方库进行链接打包成为对应的可执行程序。

因此一般在makefile当中一般都会将对应的.c文件编译为.o之后再进行编译。

例如以上的makefile按照以上的要求就变为以下的形式:

但是以上的makefile不具有普遍性,接下来来继续修改,在此当中我们可以像在C/C++当中原因创建变量,在此创建变量的方式是使用 变量名= 变量值 ,之后要访问该变量就使用 **$(变量)**的方式,在此可以了解为在C/C++当中指针的解引用一样。

在此实现了以下形式的makefile当要改变对应的文件名时就只需要将对应变量定义的位置进行修改即可。

以上我们改变完成的makefile虽然相比我们之前的实现的makefile有了更好的通配性,但是问题是当要实现非常多文件的编译成为可执行程序的工作时工作以上的makefile还是无法实现,因此接下来我们还要继续来对以上的makefile进行修改

当要实现将多个文件编译链接成为一个可执行程序时,就需要改变以上的SRC,在此在makefile提供了如下所示的两种语法来实现


改变了SRC之后接下来OBJ也要进行改变,改变结果如下所示:


接下来还需要将对应的依赖关系和依赖方法也进行修改

以上的依赖方法也可以按照原来的方式进行,但是一般还是会修改为以上的形式 其中**$^ 表示的是依赖文件, @ 表示的是目标文件** 接下来的依赖关系和依赖方法修改为一以下形式,其中**< 表示的就是将所有的.c文件编译为同名的.o文件**

完整的makefile就如下所示:

完成了具有更加普遍性的makefile之后接下来我们来创建100个.c文件来测试实现的makefile是否能实现要求

接下来使用make命令就会出现刷屏的情况,这是因为默认是会将所有的依赖方法都会回显的

如果要在执行make时不进行任何的回显就需要在原来的依赖方法上之前加上@

使用完make指令之后就可以看到当前目录下多了一个make生成的可执行程序mytest

以上就是本篇的全部内容了,接下来还会在以下的篇章中讲解剩下的两个Linux基本开发工具git和gdb,未完待续......

相关推荐
Ronin-Lotus2 小时前
上位机知识篇---Linux特殊功能文件
linux·运维·服务器·特殊功能寄存器
Zucker n4 小时前
Ubuntu本地部署Open manus(完全免费可用)
linux·运维·ubuntu
静候光阴4 小时前
python使用venv命令创建虚拟环境(ubuntu22)
linux·开发语言·python
灵山悟空5 小时前
rust语言match模式匹配涉及转移所有权Error Case
linux·开发语言·rust
zym大哥大5 小时前
Linux进程信号二
linux·运维·服务器
5:005 小时前
Linux:网络(网络编程基础)
linux·网络
call_me_wangcheng7 小时前
Ubuntu用户安装cpolar内网穿透
linux·运维·ubuntu
me8327 小时前
【Linux docker】关于docker启动出错的解决方法。
java·linux·docker
边城梦溪8 小时前
《深入理解Linux:高效崩溃分析与实时栈回溯技巧》
linux·服务器·c++·后端·面试
PiKaMouse.9 小时前
Qt串口通信开发教程:Linux下的串口调试工具实现
linux·开发语言·c++·qt