当前位置: 首页 > news >正文

【Linux操作系统】基础开发工具

目录

1. 软件包管理器

1-1 软件安装的方式

1-2 Linux软件生态

1-3 yum具体操作

1-3-1 查看软件包

1-3-2 安装软件

1-3-3 卸载软件

1-3-4 注意事项

1-4 安装源

2. 编辑器Vim

2-1 Linux编辑器-vim使用

2-2 vim的基本概念

2-3 vim的基本操作

2-4 vim正常模式命令集

2-5 vim底行模式命令集

2-6 vim小技巧

2-7 简单vim配置[了解]

3. 编译器gcc/g++

3-1 背景知识

3-2 gcc编译选项

3-2-1 预处理(进行宏替换)

3-2-2 编译(生成汇编)

3-2-3 汇编(生成机器可识别代码)

3-2-4 链接(生成可执行文件或库文件)

条件编译的作用

汇编的作用

3-3 动态链接和静态链接

3-4 静态库和动态库

静态库和动态库对比

3-5 gcc其他常用选项 

4. 自动化构建-make/Makefile

4-1 背景

4-2 基本使用

Makefile文件

依赖关系

依赖方法

项目清理

4-3 推导过程

4-4 扩展语法

5. Linux第一个系统程序-进度条

5-1 补充 - 回车与换行

5-2 行缓冲区

5-3 练手 - 倒计时程序

5-4 练手-进度条代码

6. 版本控制器Git

6-1 版本控制器

6-2 git 简史

6-3 安装 git

6-4 在 Gitee 创建项目

6-5 三板斧

7. 调试器 - gdb/cgdb使用

7-1 样例代码

7-2 预备

7-3 常见使用

7-4 常见技巧

7-4-1安装cgdb:

7-4-2watch

7-4-3 set var确定问题原因

7-4-4 条件断点

给已经存在的端点新增条件


1. 软件包管理器

1-1 软件安装的方式

在Linux中安装软件有源码安装、软件包安装-----rpm、包管理器安装yum(centos)、apt/apt-get(ubuntu)。

第一种方式:源码安装,下载到程序的源代码, 并进行编译, 得到可执行程序,这种方式首先面对就是程序员需要解决各种软件版本兼容性的问题,处理起来非常麻烦,这种方式不推荐。

第二种方式:软件包安装,有些人把一些常用的软件提前编译好,并将软件所需要的各种库文件和软件本身打包在一起, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上,并且提供不同版本供我们选择,但是这种方式问题在于实际开放中,一款软件所需要的各种方法本身可能就是基于其他第三方库开发的,想要调用这个方法,我们不光需要这个方法对应的库,还需要开发这个方法所用到的第三库文件,而软件包本身可能并不提供这个第三方库文件,这也就是我们常说的依赖问题,其次还有系统版本兼容性的问题依然需要我们去解决。

• 软件包依赖的问题

第三种方式包管理器安装,软件包和软件包管理器, 就好比 "App" 和 "应用商店" 这样的关系.

我们通过包管理器这个工具,可以很方便从网络获取到编译好的软件包, 直接进行安装,并且包管理器会自动根据我们我们系统版本、架构等自动选择对应的软件包版本,也会为我们自动处理相关依赖问题。

包管理器会自动将对应可执行文件、临时文件、配置文件、日志文件等拷贝到/根目录下对应目录中,自动进行处理。我们可以看到下面的目录拥有者都是root,所以这就是为什么我们安装一款软件必须以root安装,并且由于拷贝到根目录下,所以一次安装之后,所有的用户就都可以使用对应的程序

• yum(Yellow dog Updater, Modified)是Linux下非常常用的一种包管理器. 主要应用在Fedora,
RedHat, Centos等发行版上.
• Ubuntu:主要使用apt(Advanced Package Tool)作为其包管理器。apt同样提供了自动解决依
赖关系、下载和安装软件包的功能。

1-2 Linux软件生态

在更深入的了解软件包管理器之前这里需要先介绍一些其他知识。

首先我们应该如何评估一款操作系统的好坏呢?

决定一款系统好坏的是它的生态问题,一款系统的生态包括下图这些方面。

庞大的用户社区可以积攒一款系统绝大部分的bug以及对应处理文档,目标客户推动系统的专业化发展等都可以体现一个良好的生态对系统本身的反哺,系统的发展又会助推生态的进一步扩大等等。这也就是为什么一款新型软件会选择开源,而开源后一款优秀的软件会击败大公司扶持的软件。可以说开源本身就是一种商业政策,开源软件本身免费、安全的特性会吸引众多商业公司以及个体,并且开源本身意味着方便运用到大量的产品上。这些技术人员以及使用对应产品的普通个体,就构建了一个庞大的社区,用户会在社区上分享遇到的问题以及解决方案,加上官方的文档,一款系统几乎所有的问题都可以找到,在此基础上追求利润的商业公司和追求名望、技术突破的技术达人,也会在开源软件的基础上提出更加优秀的设计,助推系统本身的进一步发展,随着系统本身的发展,越来越多的产品应用,用户群体的增长,构筑更加庞大的生态,系统本身借助这种庞大的生态,除非出现新的技术突破,否则很难再被取代,事实上会产生一种行业标准。

其他的软件面对这个庞大的生态,没有办法打败,那就只能加入,因此实际上这种生态本身也确定了一种行业标准,类似于手机上安卓和苹果,因为这两种系统的运用到众多产品上,拥有庞大的用户,构成一个庞大的生态,任何公司想要发布软件,只能基于苹果或者安卓进行开发,如果开发任何其他系统产品,就意味着放弃这两种商品的用户及基于这两种系统所开发的一系列产品,同时要在新系统上开发,遇到的问题不会有解决文档,针对新问题需要新工具等等诸如此类的问题,对于商业公司来说这些都是不可接受的。所以很多时候,一些公司并不是无法做出一款系统(因为总有开源的系统可以抄),只是绝大部分公司不可能会平白无故的亏损利益、转战新系统从头开始。当然因为这种庞大的生态往往也能产生对应的商业标准,很多巨型的企业为了能够垄断行业标准,也会不惜放血地去开发一款系统或者产品。

综上而言生态对一款系统来说至关重要。而一款系统所配套的软件也是这个生态的重要一环,系统的软件越多,越好用,也才会有更多人使用。

这也就是为什么系统需要提供更好的软件以及软件安装工具帮助我们下载。

• Linux下载软件的过程(Ubuntu、Centos、other)

所以在回到软件包管理器的问题上,有人会会将一系列的软件包发布在服务器上,而包管理器会帮助我们从软件包管理器上下载下来

那为什么会有人免费特定社区提供软件,还发布?还提供云服务器让你下载?

这问题其实就是我们上文所述的生态问题,软件本身就是生态的重要一环,所以这些软件包服务器就是由系统官方进行维护的,并免费提供(因为生态本生就可以带来更大的其他方面的收益),虽然这些需要大量的金钱,但是由于生态中大量的公司使用这款系统,总会有人不希望系统倒闭,会进行经济援助的。

这里还有问题就是,既然我们包管理器需要从对应服务器上下载对应的软件包,那么我的机器本身是怎么知道下载链接的呢?

因为这些包管理器本身就是系统提供的,所以下载的链接时内置在系统中的文件中的,我们也把这些叫做源文件,如yum源、apt源

不过由于系统都是外国发明的,因此很多的链接都是在外网的,由于中国的长城计划,外网一般无法访问,所以官方团队、国内公司、研究机构等会再国内搭建服务器提供对应的镜像源资源,我们只需要将我们系统源文件中链接进行修改,就可以访问国内的镜像源了。

• 国内镜像源

以下是一些国内Linux软件安装源的官方链接[由文心一言生成]:
1. 阿里云官方镜像站
◦ 官方链接:https://developer.aliyun.com/mirror/
◦ 阿里云提供了丰富的Linux发行版镜像,包括CentOS、Ubuntu、Debian等,用户可
以通过该镜像站快速下载和更新软件包。
2. 清华大学开源软件镜像站
◦ 官方链接:https://mirrors.tuna.tsinghua.edu.cn/
◦ 清华大学镜像站提供了多种Linux发行版的镜像,以及Python、Perl、Ruby等编程语
言的扩展包。该镜像站还提供了丰富的文档和教程,帮助用户更好地使用这些软件
包。
3. 中国科学技术大学开源镜像站
◦ 官方链接:http://mirrors.ustc.edu.cn/
◦ 中科大镜像站提供了多种Linux发行版的镜像,以及常用的编程语言和开发工具。用户
可以通过该镜像站方便地获取所需的软件包和工具。
4. 北京交通大学自由与开源软件镜像站
◦ 官方链接:https://mirror.bjtu.edu.cn/
◦ 北交大镜像站提供了多种Linux发行版的镜像,以及相关的软件仓库和工具。该镜像站
还提供了详细的文档和指南,帮助用户配置和使用这些软件源。
5. 中国科学院软件研究所镜像站(ISCAS)
◦ 官方链接:http://mirror.iscas.ac.cn/
◦ ISCAS镜像站提供了多种Linux发行版、编程语言和开发工具的镜像。用户可以通过该
镜像站快速获取所需的软件包和更新。
6. 上海交通大学开源镜像站
◦ 官方链接:https://ftp.sjtu.edu.cn/
◦ 上海交大镜像站提供了丰富的Linux软件资源,包括多种发行版的镜像和软件仓库。用
户可以通过该镜像站方便地下载和安装所需的软件包。
7. 网易开源镜像站
◦ 官方链接:http://mirrors.163.com/
◦ 网易镜像站提供了多种Linux发行版的镜像,以及相关的软件仓库和工具。该镜像站还
提供了便捷的搜索功能,帮助用户快速找到所需的软件包。
此外,还有一些其他的国内镜像源,如搜狐开源镜像站等,但可能由于时间变化或政策调
整,部分镜像站的链接或状态可能有所变动。因此,建议用户在使用前访问官方网站或咨询
相关社区以获取最新的信息和帮助。

注:软件源也根据提供稳定版和测试版软件不同分为稳定软件源和拓展软件源

sudo yum install 1 -y epel-release

1-3 yum具体操作

1-3-1 查看软件包

通过 yum list 命令可以罗列出当前一共有哪些软件包. 由于包的数目可能非常之多, 这里我们需要使用grep 命令只筛选出我们关注的包.这里我们可以发现远端服务器也会根据我们系统的版本、架构推送对应版本的软件包给我们。

例如:

注意事项:
• 软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
• "x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.
• "el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表centos6/redhat6.
• 最后一列, base 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念.
• Ubuntu 有上述有详细介绍

1-3-2 安装软件

通过 yum, 我们可以通过很简单的一条命令完成 gcc 的安装.

# Centos
$ sudo yum install -y lrzsz


# Ubuntu
$ sudo apt install -y lrzsz

• yum/apt 会自动找到都有哪些软件包需要下载, 这时候敲 "y" 确认安装.,如果我们不想出现提问可以加上-y选项表示同意
• 出现 "complete" 字样或者中间未出现报错, 说明安装完成.


注意事项:
• 安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成.
• yum/apt安装软件只能一个装完了再装另一个. 正在yum/apt安装一个软件的过程中, 如果再尝试用yum/apt安装另外一个软件, yum/apt会报错.

1-3-3 卸载软件

仍然是一条命令:

-y表示同意,不会出现询问

# Centos
sudo yum remove [-y] lrzsz


# Ubuntu
sudo apt remove [-y] lrzsz

1-3-4 注意事项

关于 yum / apt 的所有操作必须保证主机(虚拟机)网络畅通!!!
可以通过 ping 指令验证

ping www.baidu.com

当然yum / apt也能离线安装,但是这里不做介绍。

1-4 安装源

Cetnos 安装源路径:

$ ll /etc/yum.repos.d/
total 16
-rw-r--r-- 1 root root 676 Oct 8 20:47 CentOS-Base.repo # 标准源
-rw-r--r-- 1 root root 230 Aug 27 10:31 epel.repo # 扩展源
# 安装扩展源
# $ sudo yum install -y epel-release

Ubuntu 安装源路径:

$ cat /etc/apt/sources.list # 标准源
$ ll /etc/apt/sources.list.d/ # 扩展源

更新安装源,一般就是更新上述的源文件,具体操作可以自行百度

注:如果你的Linux是在云服务器上,服务器厂商提供的就是国内的软件源
好玩的命令:https://blog.csdn.net/xiaoxiaosutang/article/details/141053674

2. 编辑器Vim

• IDE

我们Windows上所使用的软件诸如VS,被称为集成开发环境,集各种功能于一体,而Linux终端下各种功能每一个部分都有对应工具,而vim就是编写代码的一个工具。

2-1 Linux编辑器-vim使用

vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于x window、 mac os、 windows。所以后面统一按照vim来进行讲解。

2-2 vim的基本概念

vim主要有三种模式分别是命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下:

• 正常/普通/命令模式(Normal mode)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode,或者到 lastline mode下,可以执行终端指令


• 插入模式(Insert mode)
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。

• 底行模式(last line mode)
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。
在命令模式下,*shift+;*即: 即可进入该模式。在底部显示:,可以执行vim下的一些指令

按Esc返回命名模式

要查看你的所有模式:打开 vim,底行模式直接输入:help vim-modes

2-3 vim的基本操作

• 进入vim,在系统提示符号输入vim及文件名称后,就进入vim全屏幕编辑画面

• 根据错误信息,vim + 文件名 + 行数 打开文件后可以直接跳转对应行

• 不过有一点要特别注意,就是你进入vim之后,是处于[正常模式],你要切换到[插入模式]才能够输入文字。


[正常模式]切换至[插入模式]
• 输入a
• 输入i
• 输入o


[插入模式]切换至[正常模式]

• 目前处于[插入模式],就只能一直输入文字,如果发现输错了字,想用光标键往回移动,将该字删除,可以先按一下「ESC」键转到[正常模式]再删除文字。当然,也可以直接删除。


[正常模式]切换至[末行模式]
• 「shift + ;」, 其实就是输入「:」
• 退出vim及保存文件,在[正常模式]下,按一下「:」冒号键进入「Last line mode」,例如:
• : w (保存当前文件)
• : wq (输入「wq」,存盘并退出vim)
• : q! (输入q!,不存盘强制退出vim)

2-4 vim正常模式命令集

插入模式
• 按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;
• 按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
• 按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
• 从插入模式切换为命令模式 ,按「ESC」键。


移动光标
• vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格,因为老式键盘没有上下左右

可以在h、j、k、I,前面加上n,表示向对应方向移动n格

• 按「G」:光标移动到文本最后一行
• 按「$ 」:移动到光标所在行的“行尾”
• 按「^」:移动到光标所在行的“行首”
• 按「w」:光标跳到下个字的开头,单词为单位,向后移动
• 按「e」:光标跳到下个字的字尾
• 按「b」:光标回到上个字的开头,单词为单位,向前移动
• 按「#l」:光标移到该行的第#个位置
• 按[gg]:光标回到文本第一行
• 按[shift+g]:光标移动到文本最后一行,n + shift+g,表示移动到文本第n行
• 按「ctrl」+「b」:屏幕往“后”移动一页
• 按「ctrl」+「f」:屏幕往“前”移动一页
• 按「ctrl」+「u」:屏幕往“后”移动半页
• 按「ctrl」+「d」:屏幕往“前”移动半页

删除文字
• 「x」:每按一次,删除光标所在位置的一个字符
• 「#x」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符
• [shift + x]「X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符,后面的字符会往前移动一位
• 「#X」:例如,「20X」表示删除光标所在位置的“前面”20个字符
• 「dd」:剪切光标所在行,如果不将缓冲区中的内容复制到新行,就是删除
• 「#dd」:从光标所在行开始剪切#行


复制
• 「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
• 「#yw」:复制#个字到缓冲区
• 「yy」:复制光标所在行到缓冲区。
• 「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字。
• 「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与“y”有关的复制命令都必须
与“p”配合才能完成复制与粘贴功能。


替换
• 「r」:替换光标所在处的字符。
• [shift + r]即[R]进入替换模式,批量化替换,输入的任何字母、数字等都会替换原文本

按Esc回到正常操作

• [shift + ~]切换字母大小写,光标所处字母大写自动转换成小写,小写自动转换成大写


撤销上一次操作
• 「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次“u”可以执行多次回复。
• 「ctrl + r」: 撤销的恢复

u和ctrl + r可以互相撤销对方的撤销


更改
• 「cw」:更改光标所在处的字到字尾处
• 「c#w」:例如,「c3w」表示更改3个字


跳至指定的行
• 「ctrl」+「g」列出光标所在行的行号。
• 「#G」:例如,「15G」,表示移动光标至文章的第15行 首。

2-5 vim底行模式命令集

在使用末行模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进入末行模式。

保存文件
• 「w」: 在冒号输入字母「w」就可以将文件保存起来


离开vim
• 「q」:按「q」就是退出,如果无法离开vim,可以在「q」后跟一个「!」强制离开vim。
• 「wq」:一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。


• : q! (输入q!,不保存强制退出vim),w!就是强制保存,在指令后加!表示强制执行

• :vs 文件名,可以打开一个新文件进行分屏操作,此时光标在哪一个窗口下,就代表我们正在操作哪一个窗口,此时q退出,就是退出这个文件

列出行号
• 「set nu」: 输入「set nu」后,会在文件中的每一行前面列出行号。


跳到文件中的某一行
• 「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。

查找字符
• 「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。
• 「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往前寻找到您要的关键字为止。

• 「shift + #」: 会讲当前光标所在的单词在文本中出现的所有位置标注出来,这是按n就可以一个单词一个往下翻

• 「!指令」: 可以直接在底行模式下运行Linux指令

• 「%s/dst/src」: 可以将文件中所有目标文本dst替换成src文本

•视图模式

crtl + v,进入视图模式,视图模式光标只能通过h、j、k、l进行移动,进行区域选择(可以n + h/j/k/l 快速移动批量化选择区域),选定区域,再按shift + i,进入插入模式,这是对某一行进行操作,按ESC直接回到命令模式,则视图模式选中的区域中所有行都执行刚才插入模式中进行的操作,往往用于批量化操作,如批量化删除某一个区域相似的代码、批量化注释。

2-6 vim小技巧

命令行! 字母 会自动匹配最近的指令并执行,可以快速打开vim

2-7 简单vim配置[了解]

vim 一般是安装在/etc/vim下

配置文件的位置
• 在目录 /etc/ 下面,有个名为vimrc的文件,这是系统中公共的vim配置文件,对所有用户都有效。
• 而在每个用户的主目录下,都可以自己建立私有的配置文件,命名为:“.vimrc”。例如,/root目录下,通常已经存在一个.vimrc文件,如果不存在,则创建之。如果没有.vimrc,vim是使用默认配置,如果有.vimrc,会采用这里面的配置

• 切换用户成为自己执行 su ,进入自己的主工作目录,执行 cd ~
• 打开自己目录下的.vimrc文件,执行 vim .vimrc

常用配置选项,用来测试
• 设置语法高亮: syntax on
• 显示行号: set nu
• 设置缩进的空格数为4: set shiftwidth=4


使用插件
要配置好看的vim,原生的配置可能功能不全,可以选择安装插件来完善配置,保证用户是你要配置的用户,接下来:
• 安装TagList插件,下载taglist_xx.zip ,解压完成,将解压出来的doc的内容放到~/.vim/doc, 将解压出来的plugin下的内容拷贝到~/.vim/plugin
• 在~/.vimrc 中添加: let Tlist_Show_One_File=1 let
Tlist_Exit_OnlyWindow=1 let Tlist_Use_Right_Window=1
• 安装文件浏览器和窗口管理器插件: WinManager
• 下载winmanager.zip,2.X版本以上的
• 解压winmanager.zip,将解压出来的doc的内容放到~/.vim/doc, 将解压出来的plugin下的内容拷贝到~/.vim/plugin
• 在~/.vimrc 中添加let g:winManagerWindowLayout=‘FileExplorer|TagList
nmap wm :WMToggle<cr>
• 然后重启vim,打开~/XXX.c或~/XXX.cpp, 在normal状态下输入"wm", 你将看到上图的效果。更具体移步:点我, 其他手册,请执行 vimtutor 命令。

参考资料
Vim从入门到牛逼(vim from zero to hero)

3. 编译器gcc/g++

3-1 背景知识

1. 预处理(进行宏替换/去注释/条件编译/头文件展开等)
2. 编译(生成汇编)
3. 汇编(生成机器可识别代码)
4. 链接(生成可执行文件或库文件)

与C语言部分相关知识完全一致,这里不再介绍

3-2 gcc编译选项

格式

gcc [选项] 要编译的文件 [选项] [目标文件]

3-2-1 预处理(进行宏替换)

• 预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
• 预处理指令是以#号开头的代码行。
• 实例: gcc –E hello.c –o hello.i


• 选项“-E”,该选项的作用是让 gcc 开始进行程序翻译,在预处理结束后停止编译过程。
• 选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序。

3-2-2 编译(生成汇编)

• 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。

用户可以使用“-S”选项来进行查看,该选项只进行编译,编译做完就停下来,不进行汇编,生成汇编代码。


• 实例: gcc –S hello.i –o hello.s

3-2-3 汇编(生成机器可识别代码)

• 汇编阶段是把编译阶段生成的“.s”文件转成目标文件
• 读者在此可使用选项“-c”,开始翻译,汇编完成,就停下来,可看到汇编代码已转化为“.o”的二进制目标代码了


• 实例: gcc –c hello.s –o hello.o

.o文件又叫做可重定位目标文件,在windows下就是.obj结尾的文件,这个阶段的文件不可以直接执行,因为这个阶段只是我们写的程序都翻译完成了,库函数写的函数没有翻译,这个就需要链接我们C标准库中的文件。

也就是说我们所谓的库文件就是由一个个,o文件或者.obj打了个包,组成的,我们自己写的文件也可以变成.o或者.obj组成一个库文件

3-2-4 链接(生成可执行文件或库文件)

• 在成功编译之后,就进入了链接阶段。

• 实例: gcc hello.o –o hello

通过ldd指令我们可以查看程序所依赖的库文件,这里我们就可以看到code依赖了C标准库文件。

动态库:Linux下以.so结尾,Windows下以.dll结尾

静态库:Linux下以.a结尾,Windows下以.lib结尾

通过-DM选项,是一种命令行级别的宏定义,预处理的本质就是修改编辑我们的文本代码,所以这个选项就是在预处理期间,将对应的宏定义插入到我们的代码中。

经过上面这种条件编译,我们可以对我们的代码进行动态裁剪。

条件编译的作用

1.软件版本的区分

在实际应用中,一款软件可能会根据功能、收费情况,划分出社区版、专业版,虽然软件本身有不同的版本,但是就代码而言如果有多份的代码,并不利于维护,所以实际上代码只有一份,开发者会使用条件编译,专业版的对应功能使用条件编译维护,如果检测条件不满足,预处理阶段就会将对应代码裁剪掉

2.内核功能的裁剪

比如对于在一些硬件上内核,我们可能并不需联网,那么我们就可以通过条件编译将我们内核中对应的源码全部裁剪掉,这样就无法联网了,可以节省资源。对于特定状态下不需要的功能,我们就可以通过条件编译裁剪掉

3.开发工具、应用软件环境问题

对于款软件,最终可能支持安装到Linux、Windows、Android等多个不同平台上,不同接口底层调用的系统接口不同,开发者会讲这些调用不同系统接口的都写一份,通过条件编译,根据不同的平台保留调用对应系统接口的平台。

汇编的作用

那么回到一开始,现在我们可以理解了预处理的作用,那么为什么我们需要将对应的代码变成汇编呢?为什么不直接转换成机器可识别的二进制呢?这就需要先解释下计算机语言的发展了。

其实一开始的计算机本身的操控是有对应的开关的,需要人为控制;后来出现了打孔编程(如下图),空洞可以透光,这样根据一张纸带空洞的分布,就可以控制光线的有无,计算机底层的物理设备如光敏元件就可以控制电路中电流的有无、大小,进而根据这种有无,就衍生出通过0、1表示的二进制机器识别码,以及逻辑门等相关思想。

但是对于人类来说,这种二进制表示并不法阅读与记忆,因此基于这种二进制表示方式又衍生出了汇编语言,通过对应的助记符来替换原本的二进制表示方式。

之后在汇编的基础上又诞生了C语言这样的编译型语言,一直发展到如今诸如C++/java/go/python等。

那么现在问题来了,C语言诞生之后,我们是选择直接C语言翻译成二进制还是先将C语言翻译成汇编语言再翻译成二进制呢?

毫无疑问我们选择了第二条路线。假如我们选择第二条路线,那么随着之后语言的诞生,我们发现依然会面临这个问题,更不用说java底层虚拟机用C语言写的,所以我们发现,如果直接将一款新语言翻译成二进制,那么诞生一款新语言,我们都要重新造轮子,开发的难度急剧增加。

而选择第一条路线的好处是,对应C语言来说,我们的汇编语言是已经开发完善的,我们只需要将C语言与汇编语言对应好,不需要重新将C语言与二进制一对一重新设计规则,C语言的编译器的开发可以复用汇编编译器已有的设计,无疑大大减少的工作量,对于后面发展的新语言来说同样如此,不需要直接对接二进制,可以复用已有的语言的设计,站在前人的肩膀上。

所以因为汇编是先出现的,设计完善的,新语言转成汇编,可以复用,大大减少开发难度。

这里还需要说明一下编译器的问题,我们现在常使用的编译器诞生在汇编语言发明之后,一开始汇编就是来编二进制的,所以并没有编译可以将汇编语言编程二进制,这是一个鸡生蛋还是蛋生鸡的问题,所以一开始的编译器其实是用二进制写的,可以将汇编语言编写成二进制的编译器,那这个编译器诞生之后,我们就可以直接用汇编语言写一个汇编语言写的汇编编译器了,这就是编译器的语言自举过程,之后C语言同样,先使用汇编写一个C语言转汇编的编译器,再使用C语言写一个C语言编译器,C语言编译器也自举起来了,之后的语言也同理。

所以我们也可以看到将语言先转成汇编再转换成二进制,编译器上我们也更容易进入自举,容易开发。

3-3 动态链接和静态链接

在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数(比如我们需要调用库方法),但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

链接分为静态库和动态库,静态库进行链接,往往最终的可执行程序中都会从目标文件中拷贝一份副本。

静态链接的缺点很明显:
浪费空间:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;


更新比较困难:因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行调用对应方法时会跳转到对应的目标文件,进行调用

动态链接其实远比静态链接要常用得多。比如我们查看下hello 这个可执行程序依赖的动态库,会发现它就用到了一个c动态链接库:

$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令用于打印程序或者库文件所依赖的共享库列表。

为了进一步理解动静态链接,我们需要先理解库的概念。

就像随着社会的发展,工种进一步细分,每个人只负责整个产品的一小部分一样。在编写程序的过程中,会有一些功能我们是经常使用的,于是语言开发者就专门封装了对应的方法,这些方法构成的就叫做库

所以库是一套方法或者数据集,为我们开发提供最基本的保证(基本接口,功能),加速我们的二次开发

像我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该
函数的声明,printf的实现就是在库中的


 

如上图,在Linux下我们将lib.so、lib.a的后缀以及前面的lib去掉,就是该库的实际名称了,上面这个就是C语言标准库

所以系统把如printf这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定
,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样
就能实现函数“printf”了,而这也就是链接的作用

所以链接的流程就是我们写的文件和库中方法的文件链接,执行到库中的方法跳转到库中的实现处,执行完再返回。

3-4 静态库和动态库

• 静态库是指编译链接时,我们写的文件调用到库中文件处,会把库文件的代码全部拷贝一份到对应调用处,进行替换,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a


• 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,即调用到对应的库方法处,会通过库方法的地址跳转到到库中的方法,并执行,执行完返回链接文件(这个时候库中的方法也是加载到内存中的)(库文件加载到内存一次,之后其他所有的同样会调用到该方法的程序也会跳转到该方法进行执行,被所有程序共享)。

所以动态库的本质是将语言层面公共的代码未来在内存中只出现一份。

这样可以节省系统的开销。但是运行时需要库文件,一但缺失所有调用该方法的程序无法运行动态库一般后缀名为“.so”,如前面所述的libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。gcc hello.o –o hello

静态库与动态库详细内容涉及其他知识,这里暂时不详细介绍

静态库和动态库对比

1.动态库形成的可执行文件体积一定很小,动态链接比较节省内存和硬盘资源

2.可执行程序对静态库依赖程度小,但是对动态库依赖程度大,动态库不能缺失

3.程序运行需要加载到内存,静态链接程序会在内存中出现大量的重复代码。

• gcc默认生成的二进制程序,是动态链接的,这点可以通过file 命令验证。

注意2:
一般我们的云服务器,C/C++的静态库并没有安装,可以采用如下方法安装

# Centos
yum install glibc-static libstdc++-static -y
#ubuntu
略

注意1:
• Linux下,动态库XXX.so, 静态库XXX.a
• Windows下,动态库XXX.dll, 静态库XXX.lib

可以看到其他语言默认也是动态链接的

最终再次理解链接,一开始我们使用其他文件中的方法我们是多个文件共同编译,之后我们不想暴露方法的实现,就将方法的.c文件编译成.o文件,之后可以多个.o文件链接得到可执行,随着调用的.o变多,就将相关.o打包成库文件,之后包含库文件进行编译得到可执行

3-5 gcc其他常用选项 

• -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
• -S 编译到汇编语言不进行汇编和链接
• -c 编译到目标代码
• -o 文件输出到 文件
• -static 此选项对生成的文件采用静态链接
• -g 生成调试信息。GNU 调试器可利用该信息。
• -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
• -O0
• -O1
• -O2
• -O3

编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
• -w 不生成任何警告信息。
• -Wall 生成所有警告信息。

4. 自动化构建-make/Makefile

4-1 背景

• 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力


• 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作


• makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。


• make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。


• make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

4-2 基本使用

首先make是一个指令,makefile是一个文件。

使用之前我们需要在当前文件下创建一个makefile/Makefile文件,首字母大小写都可以

Makefile文件

myproc:myproc.cgcc -o myproc myproc.c
.PHONY:clean
clean:rm -f myproc
依赖关系

• 上面的文件myproc,它依赖myproc.c来形成,我们把这叫做依赖关系

依赖方法

• myproc想要从myproc.c变成myproc 就需要gcc -o myproc myproc.c 这个方法,所以我们把这个叫做依赖关系的依赖方法

我们发现使用make之后就可以按照依赖方法和依赖关系生成对应的目标文件。

需要注意的是依赖方法这一行开头需要使用tab,空格不行。

项目清理

• 工程是需要被清理的,我们通过make clean就可以将对应的文件删除干净。

我们把.PHONY修饰的clean叫做伪目标,同时clean:这一行也是一组依赖关系,    rm -f myproc则是对应的依赖方法。

我们发现当我们使用clean,可以自动完成代码的编译,而我们想想要实现对应的clean,需要使用make clean,这是因为make命令扫描makefile文件是从上往下,默认形成第一个目标文件的,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,所以我们想要形成对应的目标文件必须要指明(这里clean并没有依赖任何文件,所以make clean也不会生成任何文件)即显示make执行命令——“make clean”,以此来清除所有的目标文件,以便重编译。

伪目标的意思是总是被执行对应的依赖关系和依赖方法。

那么问题是什么叫做总是不被执行呢?

我们发现没有被.PHONY修饰的目标文件,第一次可以执行生成,后面就不让形成。

这是因为后面几次我们并没有修改。如果我们依然重新就会造成资源浪费(在实际项目中,某些文件可以一下编译几个小时,所以因为几个比特位的改动重新编译所有的文件,这是不可接受的)。在实际中,当我们对文件修改,重新编译只会对改动部分重新编译,老代码不做修改(这也被叫做增量编译)。

那么问题又来了,系统是如何识别一个文件的新旧、是否被修改过呢?

$ stat XXX
File: ‘XXX’
Size: 987 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1321125 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-25 17:05:30.430619002 +0800
Modify: 2024-10-25 17:05:25.940595116 +0800
Change: 2024-10-25 17:05:25.940595116 +0800
文件 = 内容 + 属性
Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是文件最近一次被访问的时间。

这就要要提到我们前文所提到的系统时间的概念了,我们可以通过stat 文件名查询一个文件的时间

Modify是当文件内容变化记录的时间就变,Change是文件属性变化,记录的时间就变化,不过一般文件内容变化,对应文件size、文件本身时间之类属性都会变化,所以change一般都会变化

而Acess是查询文件会改变记录的时间,不过这个特殊点。在Linux的早期版本中,每当文件被访问时,其atime都会更新。但这种机制会导致大量的IO操作。因此现在查询只有到达一定的次数,Acess才会更新。

因此有了系统时间的概念,系统就可以区分对应文件的新旧、是否被修改过。touch命令除了新建文件,还可以更改系统时间

所以回到一开始问题,.PHONY就是让make忽略时间的变化,总是重新生成对应文件。

因此我们对于要生成的目标文件一般前面不加.PHONY,就是要只编译改动的文件,节省编译时间,而对于clean因为并没有实际依赖的文件,不会生成任何目标文件,我们只是想要执行对应的依赖方法来清理环境,对于这样的,我们需要加上.PHONY,希望每次清理都可以成功执行clean。

结论:
.PHONY:让make忽略源文件和可执行目标文件的M时间对比

4-3 推导过程

myproc:myproc.ogcc myproc.o -o myproc
myproc.o:myproc.sgcc -c myproc.s -o myproc.o
myproc.s:myproc.igcc -S myproc.i -o myproc.s
myproc.i:myproc.cgcc -E myproc.c -o myproc.i
.PHONY:clean
clean:rm -f *.i *.s *.o myproc

编译

$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc

对于上面这一系列行,我们发现前一个文件依赖后一个文件,后一个文件依赖后一个文件,我们把这种关系叫做一个依赖链

那make在依赖链下是如何工作的呢?在默认的方式下,也就是我们只输入make命令。那么:

1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。


2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到myproc 这个文件,并把这个文件作为最终的目标文件。


3. 如果myproc 文件不存在,或是myproc 所依赖的后面的myproc.o 文件的文件修改时间要
比 myproc 这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成
myproc 这个文件。


4. 如果myproc 所依赖的myproc.o 文件不存在,那么make 会在当前文件中找目标为
myproc.o 文件的依赖性,如果找到则再根据那一个规则生成myproc.o 文件。(这有点像一
个堆栈的过程)

5. 当然,你的C文件和H文件是存在的啦,于是make 会生成 myproc.o 文件,然后再用
myproc.o 文件声明make 的终极任务,也就是执行文件hello 了。


6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。


7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。


8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

当然上面这种依赖链的写法在文件非常多的情况下非常不方便,所以一般不会这么写,而是使用下面的语法

4-4 扩展语法

这里BIN相当于定义的变量(:和=的区别这里不展开),而$()就相当于引用这个变量,这里我们可以理解为宏定义的替换,我们发现echo会打印出来就是变量BIN的值myproc。

如果这里我们想不显示依赖方法的执行,我们在依赖方法前面加上@就不会再回显了。

更近一步我们可以用变量代替依赖项、依赖方法,可以成功运行,因此一个变量版的makefile就完成了。

进一步修改,$@就代表目标文件名,而$^就代表目标文件依赖的众多文件列表

同样依赖方法可以有多个

这里的%就类似于一个通配符,这里%.o代表当前路径下所有的.o后缀的文件,%.c代表当前路径下所有.c结尾的文件,而$<代表将.c文件一个一个一次拿过来按照依赖方法形成一个对应的.o文件

makefile中我们也可以使用shell中的指令,上面shell 后面跟上的指令,就是展示当前列表下所有.c后缀的文件,然后通过=,这些文件就存到SRC中。

同样这里我们也可以使用makefile中语法,wildcard *.c 也是将当前路径下所有.c文件显示,然后存到SRC中,这OBJ这里SRC:.c=.o就代表将SRC中所有.c文件后缀的后缀修改成.o,OBJ中就代表这些同名.o文件。

即下面依赖关系依赖方法可以解释成:BIN目标文件依赖OBJ中的.o文件,.o文件又依赖同名.c来生成。

所以经过上面一步步演化,我们发现我们现在可以将大量的文件编译连接生成对应目标文件了。

BIN=proc.exe # 定义变量
CC=gcc
#SRC=$(shell ls *.c) # 采用shell命令行方式,获取当前所有.c文件名
SRC=$(wildcard *.c) # 或者使用 wildcard 函数,获取当前所有.c文件名
OBJ=$(SRC:.c=.o) # 将SRC的所有同名.c 替换 成为.o 形成目标文件列表
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f # 引入命令$(BIN):$(OBJ)@$(CC) $(LFLAGS) $@ $^ # $@:代表目标文件名。 $^: 代表依赖文件列表@echo "linking ... $^ to $@"
%.o:%.c # %.c 展开当前目录下所有的.c。 %.o: 同时展开同名.o@$(CC) $(FLAGS) $< # %<: 对展开的依赖.c文件,一个一个的交给gcc。@echo "compling ... $< to $@" # @:不回显命令
.PHONY:clean
clean:$(RM) $(OBJ) $(BIN) # $(RM): 替换,用变量内容替换它
.PHONY:test
test:@echo $(SRC)@echo $(OBJ)

5. Linux第一个系统程序-进度条

5-1 补充 - 回车与换行

所谓的换行\n是指将光标直接移动到下一行,回车\r是指将光标移动到开头,所谓的回车换行连起来才是我们一般情况下的回车换行(一般语言里\n其实就是\n\r,只不过\r被隐藏了)

5-2 行缓冲区

#include <stdio.h>
int main()
{printf("hello bite!\n");sleep(3);return 0;
}

我们发现上面这个代码运行会先打印然后休眠三秒退出,符合我们的预期。

#include <stdio.h>
int main()
{printf("hello bite!");sleep(3);return 0;
}

但是上面这个代码我们发现是是先程序似乎实现休眠再打印的。

其实我们的这里的代码是顺序结构,一定是先执行printf再休眠的,之所以我们在程序退出之后才看到打印内容是因为printf打印内容是存储在缓冲区的,第一个例子的\n可以行刷新,将我们缓冲区的内容刷新出,即显示到显示器上,所以我们可以立刻看到。

而第二个例子是没有\n,数据一直在缓冲区中,直到程序结束,自动刷新缓冲区,我们才能看到打印内容。

#include <stdio.h>
int main()
{printf("hello bite!");//stdout代表输出流,刷新输出流,数据就可以显示到显示器上fflush(stdout);sleep(3);return 0;
}

所以当我们不使用\n时,我们需要fflush函数主动刷新缓冲区,才可以看到刷新内容

5-3 练手 - 倒计时程序

#include <stdio.h>
#include <unistd.h>int main()
{//10秒倒计时码int i = 10;while (i >= 0){//因为这里我们需要在一行显示到技术,所以不能使用\n,需要手动刷新printf("%-2d\r", i); // \nfflush(stdout);i--;sleep(1);}//换行,避免XShell终端提示符打印覆盖倒计时数字printf("\n");return 0;
}

5-4 练手-进度条代码

process.c

#include "process.h"
#include <string.h>
#include <unistd.h>
#define NUM 101
#define STYLE '='
// vesion1
void process_v1()
{char buffer[NUM];memset(buffer, 0, sizeof(buffer));//转圈圈表示当前程序还在运行//这四个字符连续起来可以形成转圈圈的视觉效果const char* lable = "|/-\\";int len = strlen(lable);int cnt = 0;while (cnt <= 100){   //为形成进度条的效果,这里使用100的占位符输出printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt % len]);fflush(stdout);//进度条变化buffer[cnt] = STYLE;cnt++;usleep(50000);}printf("\n");
}// verison2
//传入数据,模拟下载文件是的进度条效果
void FlushProcess(double total, double current)
{char buffer[NUM];memset(buffer, 0, sizeof(buffer));const char* lable = "|/-\\";int len = strlen(lable);static int cnt = 0;// 不需要自己循环,填充#int num = (int)(current * 100 / total); // 11.0 / 1000int i = 0;for (; i < num; i++){buffer[i] = STYLE;}double rate = current / total;cnt %= len;printf("[%-100s][%.1f%%][%c]\r", buffer, rate * 100, lable[cnt]);cnt++;fflush(stdout);
}

process.h

#pragma once
#include <stdio.h>
void process_v1();
void FlushProcess(double total, double current);

main.c

$ cat main.c
#include "process.h"
#include <stdio.h>
#include <unistd.h>//模拟文件下载的进度条
//下载总量
double total = 1024.0;
//下载速度
double speed = 1.0;void DownLoad()
{double current = 0;while (current <= total){FlushProcess(total, current);// 下载代码usleep(3000); // 充当下载数据current += speed;}printf("\ndownload %.2lfMB Done\n", current);
}int main()
{DownLoad();DownLoad();DownLoad();DownLoad();DownLoad();DownLoad();DownLoad();DownLoad();return 0;
}

Makefile

SRC = $(wildcard * .c)
OBJ = $(SRC:.c = .o)
BIN = processbar$(BIN) :$(OBJ)gcc - o $@ $ ^
%.o: % .cgcc - c $ <
.PHONY :
clean :rm - f $(OBJ) $(BIN)

6. 版本控制器Git

不知道你工作或学习时,有没有遇到这样的情况:我们在编写各种文档时,为了防止文档丢失,更改
失误,失误后能恢复到原来的版本,不得不复制出一个副本,比如:
“报告-v1”
“报告-v2”
“报告-v3”
“报告-确定版”
“报告-最终版”
“报告-究极进化版”
...
每个版本有各自的内容,但最终会只有一份报告需要被我们使用 。
但在此之前的工作都需要这些不同版本的报告,于是每次都是复制粘贴副本,产出的文件就越来越
多,文件多不是问题,问题是:随着版本数量的不断增多,你还记得这些版本各自都是修改了什么
吗?
文档如此,我们写的项目代码,也是存在这个问题的!!

6-1 版本控制器

为了能够更方便我们管理这些不同版本的文件,便有了版本控制器。所谓的版本控制器,就是能让你了解到一个文件的历史,以及它的发展过程的系统。通俗的讲就是一个可以记录工程的每一次改动和版本迭代的一个管理系统,同时也方便多人协同作业。
目前最主流的版本控制器就是 Git 。Git 可以控制电脑上所有格式的文件,例如 doc、excel、dwg、dgn、rvt等等。对于我们开发人员来说,Git 最重要的就是可以帮助我们管理软件开发项目中的源代码文件!

对于使用Git的用户来说,我们本地会存在一个本地仓库,还会有一个远程仓库(诸如gitee、github就提供这样的远程仓库托管服务,远程仓库就部署在对应的云服务器上,我们可以在这些网站上建立自己的远程仓库)。

对于每一个用户来说,都可以访问自己的远程仓库,从远程仓库中拉取对应的文件到本地仓库,也可以将本地仓库的的文件上传至远端仓库,这样即使本地或者远程仓库出事,只要有一端没事,数据就不会丢失。Git会记录用户的每一次提交,我们可以根据对应的记录恢复对应的数据,也可以将提交到本地仓库的数据push到远程仓库。

对于其他用户来说只要知道对应远程仓库的URL(并且这个仓库可以被该用户访问),就可以从远端拉取对应的文件,也可以提交本地文件到远端,这样不同的用户可以协同开发同一个项目,同样这样每一台主机即使client也是server,只要其中有一台主机没坏,那么数据就不会丢失。同样一个用户可以拥有多个不同的仓库。

可以说Git采用了一种去中心、分布式的策略。

git本身在不同平台下都可以使用

6-2 git 简史

同生活中的许多伟大事物一样,Git 诞生于一个极富纷争大举创新的年代。
Linux 内核开源项目有着为数众多的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 LinusTorvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统。 他们对新的系统制订了若干
目标:
• 速度
• 简单的设计
• 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
• 完全分布式
• 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。 它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统。

6-3 安装 git

6-4 在 Gitee 创建项目

Github的登录需要使用科技,这里不做演示

注册账号
这个比较简单, 参考着官网提示即可. 需要进行邮箱校验.
创建项目
1. 登陆成功后, 进入个人主页, 点击左下方的 创建仓库按钮新建项目,注意, 名称不能重复, 系统会自动校验. 校验过程可能会花费几秒钟

3. 在创建好的项目页面中复制项目的链接, 以备接下来进行下载.

下载项目到本地
创建好一个放置代码的目录.

git clone [url]

这里的 url 就是刚刚建立好的 项目 的链接.

6-5 三板斧

1. git add
将代码放到刚才下载好的目录中

git add [文件名]

将需要用 git 管理的文件告知 git
2. git commit
提交改动到本地,注git只会提交变动的部分,对于非文本的内容会记录造成这一变化的指令,而非内容本身

git commit -m "XXX"

最后的 "." 表示当前目录
提交的时候应该注明提交日志, 描述改动的详细内容.
3. git push
同步到远端服务器上

git push

需要填入用户名密码. 同步成功后, 刷新 Github 页面就能看到代码改动了.

注:git add只是将对应变化提交到暂存区,并没添加到本地仓库中,只有commit之后,才是实质的提交到本地仓库。

可以看到git可以记录提交的内容


配置免密码提交
https://blog.csdn.net/camillezj/article/details/55103149
4. 其他

首次提交需要根据以下信息完成配置,姓名与邮箱就是gitee/github注册时所用用户名和邮箱

注:当远端仓库对应不同本地仓库时,为了确保代码本身的安全性,如果远端仓库相比某一台仓库发生变化,那么当前本地仓库无法提交代码到远程,必须先从远端仓库拉取带来下来,确保本地仓库代码是最新的了,才可以提交(远端仓库由于特性必然是最新的)。

本文Git操作仅为方便代码维护,详细内容、原理可以参考笔者Git系列文章。

7. 调试器 - gdb/cgdb使用

7-1 样例代码

// mycmd.c
#include <stdio.h>
int Sum(int s, int e)
{int result = 0;for (int i = s; i <= e; i++){result += i;}return result;
}
int main()
{int start = 1;int end = 100;printf("I will begin\n");int n = Sum(start, end);printf("running done, result is: [%d-%d]=%d\n", start, end, n);return 0;
}

7-2 预备

• 程序的发布方式有两种, debug 模式和release 模式, Linux gcc/g++ 出来的二进制程
序,默认是release 模式。所以Linux下我们编译的代码无法直接调试

• 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上-g 选项,如果没有添加,程序无法被
编译

加上-g,我们发现生成可执行文件明显变大,这是因为里面添加了对应的调试信息便于调试

7-3 常见使用

• 开始: gdb binFile
• 退出: ctrl + d 或 quit 调试命令

命令作用样例
list/l显示源代码,从上次位置开始,每次列出10行list/l 10
list/l 函数名列出指定函数的源代码list/l main
list/l 文件名:行号列出指定文件的源代码list/l mycmd.c:1
r/run调试并运行,从程序开始连续执行run
n/next单步执行,不进入函数内部,逐过程next
s/step单步执行,进入函数内部,逐语句step
break/b [文件名:]行号在指定行号设置断点break 10
break test.c:10
break/b 函数名在函数开头设置断点break main
info break/b查看当前所有断点的信息info break
finish执行到当前函数返回,然后停止finish
print/p 表达式打印表达式的值print start+end
p 变量打印指定变量的值p x
set var 变量=值修改变量的值set var i=10
continue/c从当前位置开始连续执行程序continue
delete/d
breakpoints
删除所有断点delete breakpoints
delete/d
breakpoints n
删除序号为n的断点delete breakpoints 1
disable breakpoints禁用所有断点disable breakpoints
enable breakpoints启用所有断点enable breakpoints
info/i breakpoints查看当前设置的断点列表info breakpoints
display 变量名跟踪显示指定变量的值(每次停止时)display x
undisplay 编号取消对指定编号的变量的跟踪显示undisplay 1
until X行号执行到指定行号until 20
backtrace/bt查看当前执行栈的各级函数调用及参数backtrace
info/i locals查看当前栈帧的局部变量值info locals
quit退出GDB调试器quit

l每次显示10行代码

b打断点,r运行会停在断点,c继续运行到结束

b可以后面跟文件 : 行号打断点,也可以直接加行号打断点,但是b后面跟函数名打断点会打在函数的入口处

info b查看断点,Num是断点编号,Type是断点编号,Enb表示断点是否使能,Address是断点打的地址,What表示打的什么断点

删除断点只可以通过断点编号删除

gdb不退出,断点编号依次递增

gdb会自动记录上一条指令,我们按回车,可以自动执行上一条指令

bt查看调用栈

当进入到函数中时,如果想立刻退出函数,finish会立刻调用完函数,提出到主函数中

函数调用结束后这个部分其实是两个动作,先将函数返回值给寄存器,再从寄存器中将对应值给n

所以这里我们可以看到p打印n值会发生变化

disable可以禁用断点,断点禁用后,我们发现Enb随着变化,同时断点不在起作用,当我们不想在该点停下,同时又不想失去该点调试痕迹时,可以使用这个指令,方便后面重新找到调试痕迹

enable使断点重新使能。

p打印的值,我们继续往下打印,对应变量不在显示,我们可以使用display,可以实时查看对应的变量值

调试的本质是:找到问题、查看代码上下文,断点的本质是把代码进行快级别划分,以快为单位进行快速定位区域,finish可以快速确任问题是否在函数内,until可以在局部区域快速执行,有了这三个命令,我们可以快速的找到问题所在,而其他命令可以帮助我们查看代码上下文,进而解决问题

7-4 常见技巧

7-4-1安装cgdb:

• 上面的基本调试还是麻烦,虽然是黑屏,但是还是想看到代码调试
• 推荐安装cgdb:
• Ubuntu: sudo apt-get install -y cgdb
• Centos: sudo yum install -y cgdb

7-4-2watch

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

注意:
如果你有一些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变
化了,就会通知你.

7-4-3 set var确定问题原因

更改一下标志位,假设我们想得到+-result

7-4-4 条件断点

添加条件断点,只有满足条件,该断点才会触发

给已经存在的端点新增条件

注意:
• 条件断点添加常见两种方式:1. 新增 2. 给已有断点追加
• 注意两者的语法有区别,不要写错了。
• 新增: b 行号/文件名:行号/函数名 if i == 30(条件)
• 给已有断点追加:condition 2 i==30, 其中2是已有断点编号,没有if

http://www.dtcms.com/a/414517.html

相关文章:

  • 老年ai模拟恋爱抖音快手微信小程序看广告流量主开源
  • 知名的网站制作公司需要多少钱企业宣传网站模板下载
  • 深圳横岗做网站的网站品牌形象设计怎么做
  • 社区网站推广方案百度百家号注册
  • 编程竞赛高频考点
  • Linux 程序使用 STDOUT 打印日志导致程序“假死”?一次线上 Bug 的深度排查与解决
  • (一)routeros命令笔记:开局篇
  • 网站推广模式一份完整的项目计划书
  • 基于STM32设计的智能安全头盔_299
  • ​VR应急安全学习机,提升应对自然灾害时自救互救的应急技能
  • app网站建设公司竞彩网站建设
  • pytorch基本运算-torch.normal()函数输出多维数据时,如何绘制正态分布函数图
  • OpenCV2-图像基本操作-阈值与平滑处理-形态学-梯度运算
  • 【开题答辩全过程】以 springboot+美食电子商城的设计与实现为例,包含答辩的问题和答案
  • MySQL所有关键字详细含义说明
  • MySQL表压缩:用CPU换I/O的秘密武器
  • 做外贸网站需要缴什么税重庆高端网站建设价格
  • java面试day5 | 消息中间件、RabbitMQ、kafka、高可用机制、死信队列、消息不丢失、重复消费
  • 时序数据库选型指南:如何为企业选择合适的时序数据库解决方案
  • 【iOS】alloc、init、new
  • 做网站的开发心得wordpress是不是一定要买服务器
  • AI觉醒:小白的大模型冒险记 第10章:故事续写竞技场 - 实战演练
  • 网站的形成贵州省住房和城乡建设官方网站
  • python知识点
  • LeetCode 5.最长回文字符串
  • 浅谈蓝牙的连接基石
  • Matlab通过GUI实现点云的导向(引导)滤波(附最简版)
  • MacOS - Clang使用bits/stdc++.h - 非官方(竞赛用) - 通用方法
  • 智能进化:高端平板操控系统的技术革新
  • 网站开发专业职业规划微信小程序游戏开发教程