构建完整工具链:GCC/G++ + Makefile + Git 自动化开发流程

我们都知道代码形成可执行程序是需要进行预处理,编译,汇编,链接的。那么今天就来看看编译器吧。

一、gcc/g++编译器

gcc 和 g++编译器有什么区别呢?

gcc编译器是专门用来编译C语言的,而g++编译器既可以用来编译C也可以用来编译C++,主要用来编译C++

既然都说到这里了,那么要想了解gcc、g++必然少不了围着预处理,编译,汇编,链接来进行说明。

1. 预处理

复制代码
//filename为要编译的文件名
gcc -E filename 将预处理后的内容打印到显示屏上
gcc -E filename -o xxx 将预处理后的内容写到了xxx文件里,xxx文件自己指定


gcc -E filename -o xxx是在进行预处理阶段,在这个阶段中可以看到,它都做了以下的工作。

. 头文件展开

. 宏替换

. 去注释

. 条件编译

带大家理解一下,什么是头文件展开?

所谓的头文件展开其实就是把头文件中相关的内容拷贝到源文件里,所以,预处理完毕,其实就可以不用头文件了。

条件编译本质是对代码进行裁剪

如何理解裁剪呢?

在预处理阶段,代码从原本的20行变成了800行,就是将头文件里面的内容拷贝到了源文件里,去掉注释就是在删除内容,条件编译就是根据条件可以修改代码。所以所谓的裁剪其实就是对代码进行增、删、改操作。

头文件都被放在了usr/include/目录下

2. 编译

复制代码
gcc -S filename -o xxx 对预处理阶段的文件,进行编译

可以看到,对比于预处理阶段的文件,预处理阶段的文件还是C语言 ,但是编译之后的文件我们就已经看不懂了。编译的目的就是为了生成汇编语言

3. 汇编

复制代码
gcc -c filename -o xxx进行汇编

注意:此汇编非彼汇编。看到这里可能已经有人看懵了。编译生成的汇编是一种语言(汇编语言),而这里的汇编是生成可执行程序当中的一个阶段它是为了将汇编语言翻译成可重定位目标二进制文件 。那么,它既然已经是二进制文件了,也就是说它已经能够被计算机所识别,你说,这个文件是否能够被执行呢

显然不行,这个时候可能就有细心的小伙伴发现了,程序能不能运行,不应该要看x权限吗,这个文件没有x权限,当然不能够被运行了 。那我们再来做一个实验。


可以看到,它是执行失败了的。那它为什么会执行失败呢?先看下一阶段。

4. 链接

复制代码
gcc filename -o xxx生成可执行文件或库文件


可以看出来,可重定位二进制文件确实不是可执行程序,必须经过链接才能生成可执行程序 。那么,这是为什么呢 ?就由小编来为各位解惑吧。在写代码的时候,我们不可置否的使用了库函数,而库函数的实现在哪里呢?库函数又不是我们自己写的,是别人帮我们写好的,库函数的实现应该在库里面所以,我们在使用库函数时,相当于只是拿到了库函数的声明,它需要和库里面的函数进行链接才可以生成可执行程序文件(也就是说,可重定位二进制文件必须依赖于对应的库才可以)

虽然结论已经出来了,但是要怎么证明它确实是和库进行了链接呢?

复制代码
ldd xxx查看可执行文件或共享库所依赖的动态链接库

看到这里,相信大家对链接已经有了一个初步的认识了,那么,什么是链接呢

库函数的实现并不在我们自己写的代码里,那么它在编译的时候库函数的地址是未知的,库函数的实现在库里,它会有一个地址,链接的时候会把xxx.o文件和库进行合并,形成一个可执行文件。所谓的合并是在干什么呢就是为了将库中函数的地址重新填写到.o文件里

既然,链接需要库,那也就是说,系统里必须提前给我们安装好了库,这样我们才能编译,链接?是的,没错。

函数声明

函数实现

如何理解库呢?首先为什么要有库 ?在开发当中,开发者会经常的使用到一些函数方法,这是一种很普遍的现象,但是,如果每个人都为了实现一个函数写一个方法,那么也太浪费时间了吧。所以,有人就把这些经常性使用的一些函数放在了库里,就是为了提高开发效率

库的常见分类:静态库、动态库

静态链接:与静态库链接

动态链接:与动态库链接

实验证明一下。

动态链接

静态链接

静态链接有可能会失败,这是因为系统中可能没有默认安装C/C++的静态库。使用大模型生成安装静态库的命令就可以了。

可以观察到,编译器默认采用动态链接的方式,形成可执行程序

举个简单的例子,来理解一下动静态库。

今年中考结束后,有许多学生都考上了自己梦寐以求的中学,其中不乏有许多尖子生,其中一名学生张三学习非常好,考上了中学,但是他有点自己的小心思,他想去一个附近有网吧的学校,当然了,他的父亲并不知道他的小心思,他不知道应该去哪所学校。于是就和他的父亲去找他的朋友李四,询问李四的意见。在询问过程中,不仅找到了一所非常不错的学校也达成了张三的心愿。这所学校管理非常严格,平时是出不去的,所以只有周末的时候才可以出去。张三为了出去玩,于是就给周末定了一个计划表,早上要完成语文,数学,英语作业,下午就要出去玩,然后洗衣服等,等到了玩的时间,张三立马就要出去玩,之后回到学校洗衣服。

张三就相当于是一个程序,他的父亲就是链接器,张三从李四这里得到链接信息(链接:写入方法的地址),这所中学就是内存,计划表就是具体的代码,网吧就是动态库(共享库)。

所以,什么是动态链接呢?动态链接就是程序还未加载到内存时,就已经和动态库建立了信息上的连接,执行到库函数的时候,跳转到动态库执行,然后继续执行自己的代码

后来,老师发现学生经常去网吧,就给派出所打电话,举报网吧无证经营,警察一看果然情况属实,就把这个网吧干掉了。这个时候想玩电脑就要花钱去买了,很浪费资源的,毕竟,上一次网吧才多少钱,这也导致许多同学都去不了网吧。

所以,动态链接(动态库)的优点是节省资源,缺点是一旦丢失,所有程序都无法运行

放假回家,你闷闷不乐,你父亲问你为什么,你说想玩电脑,你父亲看你学习成绩挺好的,索性就在学校附近的网吧里给你买了一台电脑。这就是静态链接

所以,静态链接就是把你要的方法。拷贝到可执行程序里

静态链接的优点:不依赖任何库,自己独立就能运行

缺点:体积大,占据资源多(占据磁盘空间,内存空间),加载速度受影响

二、自动化构建make/Makefile

1. 基本概念

make是一个linux系统内置的命令

makefile/Makefile是一个需要自己建立的一个文件

make命令会在当前目录下寻找makefile文件,解释里面的内容

2. make,makefile的操作



3. 理解makefile

举个简单的例子理解一下。

每到月底的时候,大学生经常会给自己的父亲打电话,如果你只是打电话,说我是你儿子,就把电话挂了。你爸能知道你要干什么吗?肯定不行,这就叫做表明依赖关系。你给你爸打电话说,没钱了,你爸就知道你要干什么了,一会钱就到你手机上了。这就叫做表明依赖方法。依赖关系+依赖方法才能达到你想要的目的。

看到这里,大家有没有疑问呢?为什么make的时候只执行了第一个目标呢这是因为make,makefile默认只形成一个目标,就是从上往下遇到的第一个目标

那么什么是伪目标呢?

.PHONY:表示被修饰的目标是一个伪目标

伪目标有什么特点呢?

特点伪目标总是被执行的

要想理解什么是总是被执行的 ,就先来看看什么叫做总是不被执行

可以看到,第一次没有编译形成可执行程序,make是成功的,第二次以后都是没有被执行。那么,这是为什么呢?我们给源代码添加一些内容,再看看结果呢?


可以看到,在将源代码内容改变之后,make是成功的。所以,make之所以会失败,是因为编译器发现没有必要再去对源代码进行编译,因为源代码已经是最新的了。如果重复的对最新的源代码进行编译,就会浪费CPU的资源。这样做的目的主要是为了提高编译的效率 。这叫做总是不被执行

那么,什么叫做总是被执行呢?来看一个例子。

我们给code加上.PHONY:code,看一下运行结果。

这叫做总是被执行。但是有一个问题,make,makefile它怎么知道要不要重新编译呢?我们来看一个命令。

在前面的文章里,我们学过这个。任何一个文件,它都包含三种时间。

文件 = 文件内容 + 文件属性

access time文件最近被访问的时间

modify time文件内容最近被修改的时间

change time文件属性最近的修改时间

对内容做一些删除

可以看到,modify time发生了变化,那么为什么change time也会发生变化呢 ?首先,modify time也属于文件属性,其次文件的size也发生了变化,所以,change time发生了变化那么在删除内容的时候,肯定也访问了文件,为什么access time没有发生变化呢?这与系统的内核版本有关,老的内核版本是会发生变化的,新的内核版本可能得好多次才会发生变化 ,这是因为access time的参考价值不大,所以才变成了这样。

我们的问题是,make,makefile它怎么知道要不要重新编译呢 ?答案是:通过对比源文件与可执行文件的modify time时间,源文件最新就重新编译

什么叫做总是被执行呢忽略时间,不做对比,直接执行

刚开始,只有源文件,就编译,此时已经有了可执行文件,若下次还要编译,就对比源文件与可执行文件的modify time,源文件最新就重新编译。

三、版本控制器Git

1. git的核心功能

git的核心功能就是为了版本控制

举个简单的例子来理解什么是版本控制

大学生每到期末就要写各种实验报告,张三率先完成了报告,拿去给老师检查,老师为张三指出了各种毛病,什么格式不对,字数不够各种问题,让张三继续去修改,张三改了几天又拿去给老师看,老师还是不满意,于是张三又继续去修改他的报告了,过了几天,又拿去给老师,还是有各种问题,这时候老师说,算了张三,你这报告越改越不行,把你的第一份报告拿来吧,这时候张三懵了,他根本就没有对以前的报告进行保存。张三每天都唉声叹气的,被舍友李四看到了,李四就比较聪明了,他把他的报告拿去给老师,老师依然指出了各种毛病,让李四去修改,但李四就对他的每一份报告进行了保存,每一次拿新的报告给老师看,几次之后,老师说,算了李四,你把你的第一份报告拿来吧。李四很高兴,他的作业终于完成了,因为他对他的报告都做了备份。

张三和李四就是程序员,报告就是代码,老师就相当于是产品经理。李四做备份的过程就相当于版本控制版本控制的目的就是为了应对各种变化

2. 认识git

后来张三进入了一家公司工作,在他的目录下写代码,他为了做版本管理,在Linux上安装了git,在本地上建立了本地git仓库,后来,张三把自己写的代码 code1 都提交到了本地git仓库,好巧不巧,他的舍友李四也进入了这家公司工作,他们俩在同一个组里,张三开发功能一,李四开发功能二 code2,自己写自己的代码,那么他们两个要怎么协同开发呢?于是他们的组长在服务器上建立了一个git仓库,这个git仓库就叫做远端仓库。张三把自己的代码code1从本地仓库提交到远端仓库,李四也是一样,但是git有一个特点,就是自己本地的git仓库内容和远端仓库不一致就无法提交,对于李四而言也是如此。

规则1如果我们的本地仓库和远端仓库内容不一致,推送方就无法推送

比如张三提交了自己的代码code1,这时候李四去提交代码是无法推送的,因为李四的本地git仓库并没有张三的代码,这时候就要求李四必须和远端仓库进行同步才可以提交。下一次张三要提交代码,张三的本地git仓库和远端仓库又不一样了,就要求张三必须去同步。

总结多人开发的时候我们通过限制提交的方式,保证服务端,尽量都是最新的,如果不是最新的,允许client提交,其他client就必须同步的方式进行多人协同开发!每个人要提交,必须先和远端保持一致,增量式的提交

细节1什么是仓库仓库就是特定的目录

细节2git本身除了版本控制,也提供网络功能

细节3本地仓库和远端仓库没有本质区别。本地git服务和远端git服务没有本质区别

细节4git是一个去中心化的版本控制策略

什么是去中心化呢

举个例子:推送方要推送自己的代码,前提是必须要和远端仓库保持一致,否则就必须要进行同步。如果有一天远端仓库不在了呢,那么你的数据依然存在于自己的本地仓库中,并不会受到远端仓库的影响,这就叫做去中心化。

3. git操作

复制代码
//安装git
yum install -y git(centos)
apt install -y git(ubuntu)

在Github上创建项目

复制代码
//克隆远端仓库
git clone pathname

什么是克隆克隆就是将远端仓库里面的文件拷贝到本地仓库

但是我们会发现,克隆下来的仓库内容多了一个.git,这个.git是什么呢.git就是本地仓库

这个就叫做当前工作区,我们写的代码就是在这里。

复制代码
git add .将所有新增的内容提交到.git里
git commit -m将新增的内容提交到本地仓库
git push本地仓库与远端仓库进行同步

git add .git commit -m 是将文件提交到本地仓库里,git push将本地仓库里的内容提交到远端仓库,同时需要输入用户名和密码

.git里面有一个文件index需要注意,它是干什么的呢在本地仓库里还有一个暂存区,就是indexgit add .就是将文件提交到暂存区里的 。那么这个暂存区有什么用呢?它就是为了人们后悔用的。git commit -m才是将暂存区里的提交到本地仓库

那么,这个.gitignore这个文件是用来干什么的呢git主要是为了做源文件,头文件,文档等的托管,它不需要临时文件.gitignore就是为了过滤掉这些临时文件。我们可以证明一下。

可以看到,并不是所有的文件都提交到了远端仓库。这些命令是将文件提交到仓库了吗 ?在当前工作区创建文件,添加内容,删除内容,修改内容,这些都是文件的修改记录,本质是当前工作区内文件的变化提交的本质是提交你的历史修改操作

复制代码
git log//查看提交历史
git pull//拉取远程更新
git status//查看工作区状态

四、调试器gdb/cgdb

1. gdb

gdb/cgdb是用来调试的工具 。就像vs也可以用来调试。

既然gdb/cgdb是用来调试的工具,那么这里调试为何会失败呢vs编译有两个版本,Debug,Release。那么这两个有什么区别呢Debug版本是给程序员使用的,它里面添加了调试信息,而Release是给用户,测试人员用的,不添加调试信息,是无法调试的。linux默认是以release版本发布的,没有调试信息,所以会失败。

问题1你怎么知道linux是以release版本发布的

问题2怎么才能以debug版本发布

复制代码
gcc filename -o xxx -g//以debug版本发布

通过对比,可以发现code1的大小比code大,这是为什么呢因为code1里面添加了调试信息。我们可以来证明一下。

接下来就可以进行调试了。

gdb会自动记录历史命令。用gdb调试,默认是看不到代码的,还需要手动去解决,体验很不好。所以接下来就看cgdb。

2. cgdb

调试的本质是在干什么呢 ?答案是:定位问题

接下来,就来学习cgdb吧。

复制代码
list/l + 行号------从指定行号查看代码
list/l 函数名
list/l 文件名:行号

. b + 行号 / b + 文件名:函数名 用来打断点

. info b 用来查看断点信息

可以看到,断点编号是线性递增的

. delete/d + 断点编号 删除断点
. delete/d + breakpoints 删除所有断点

细节1gdb启动调试的时候只是开启了gdb,被调试的程序并没有运行起来

细节2r/run,表示的是在gdb的场景中,启动我们自己的程序

细节3在没有断点的情况下,r/run就是让我们的程序直接运行结束

细节4断点的本质功能是让我们的程序。运行到指定的行进行暂停

. n/next 逐过程,不进入函数内部

可以看到,并没有进入Sum函数里面去。

. s/step 单步执行,进入函数内部

在vs中,我们还可以在监视窗口查看变量值,地址。那在cgdb上是否也可以呢?

. p + (&)变量名

可以看到,确实可以查看变量值及地址。但是它只会显示一次,这不符合我们的要求。所以有了接下来的一条命令。

. display + 变量名 跟踪显示指定变量的值

. undisplay + 编号 取消对指定编号的变量的跟踪显示

. until + 行号 跳转到指定的行号

. c/continue 从当前位置开始连续执行程序,直到运行结束或者运行到下一个断点处

. finish 执行到当前函数返回,然后停止

. bt/backtrance 查看堆栈调用信息

. info local 查看当前栈帧的局部变量值

. info i 查看当前正在debug的程序信息

. disable + 断点编号 禁用断点

disable breakpoints 禁用所有断点

应用场景 :当你程序出现bug时,这时候你要去打断点进行调试,找到问题之后取消断点,修改问题,但是结果不对,这时候你就要重新打断点,有可能你就忘记了之前断点的位置。所以禁用断点的目的是为了保留调试痕迹

. enable + 断点编号 启用断点

enable breakpoints 启用所有断点

3. 调试技巧

. 查找bug时可以采用二分查找,快速定位问题

. watch 执行时监视一个表达式的值,如果监视的表达式在程序运行期间值发生变化,GDB会暂停程序的执行,并通知使用者

应用场景如果有一些变量不应该被修改,但是你怀疑它修改导致的问题,你可以watch它

. set var 更改指定变量的值确定问题原因

比如说:你怀疑是因为某个变量而导致程序出错,你可以在调试的时候直接更改它的值,进而确定是否是因为它的原因。

你求的是1到100的和,但是结果确实0。你就可以用set var来确定问题原因。

. 条件断点

添加条件断点

给已存在的断点添加条件

相关推荐
G31135422731 天前
Linux 内核设计中的核心思想与架构原则
linux·架构·php
zhuzewennamoamtf1 天前
Linux内核platform抽象、数据结构、内核匹配机制
linux·运维·数据结构
TL滕1 天前
从0开始学算法——第十八天(分治算法)
笔记·学习·算法
云闲不收1 天前
git rebase
git
算法与双吉汉堡1 天前
【短链接项目笔记】Day2 用户注册
java·redis·笔记·后端·spring
江上清风山间明月1 天前
git pull和git checkout在恢复文件的区别
git·pull·checkout
思成不止于此1 天前
【MySQL 零基础入门】MySQL 约束精讲(一):基础约束篇
数据库·笔记·sql·学习·mysql
Kira Skyler1 天前
ELF文件解析 elf.o 文件主要内容.md
linux
海鸥811 天前
in argocd ‘/tmp/_argocd-repo/../.git/index.lock‘: No space left on
git·argocd
weixin_307779131 天前
Jenkins Pipeline 完全指南:核心概念、使用详解与最佳实践
开发语言·ci/cd·自动化·jenkins·etl