像枫一样头像
Linux 基础开发工具全解:换源、Vim、GCC、Makefile、GDB、Git(附进度条实战)封面图

Linux 基础开发工具全解:换源、Vim、GCC、Makefile、GDB、Git(附进度条实战)

本节⽬标

• 学习yum⼯具,进⾏软件安装 
• 掌握vim编辑器使⽤,学会vim的简单配置 
• 掌握gcc/g++编译器的使⽤,并了解其过程,原理 
• 掌握简单的Makefile编写,了解其运⾏思想 
• 编写⾃⼰的第⼀个Linux 程序:进度条 
• 学习 git 命令⾏的简单操作, 能够将代码上传到 Github 上 
• 掌握简单gdb使⽤于调试

1. 软件包管理器

1-1 什么是软件包 

1-1 什么是软件包 
• 在Linux下安装软件, ⼀个通常的办法是下载到程序的源代码, 并进⾏编译, 得到可执⾏程序. 
• 但是这样太⿇烦了, 于是有些⼈把⼀些常⽤的软件提前编译好, 做成软件包(可以理解成windows上
的安装程序)放在⼀个服务器上, 通过包管理器可以很⽅便的获取到这个编译好的软件包, 直接进⾏安
装. 
• 软件包和软件包管理器, 就好⽐ "App" 和 "应⽤商店" 这样的关系. 
• yum(Yellow dog Updater, Modified)是Linux下⾮常常⽤的⼀种包管理器. 主要应⽤在Fedora, 
RedHat, Centos等发⾏版上. 
• Ubuntu:主要使⽤apt(Advanced Package Tool)作为其包管理器。apt同样提供了⾃动解决依
赖关系、下载和安装软件包的功能。

**软件包里的主体,就是提前编译好的可执行程序(二进制)**,但不止只有可执行程序。
它的完整来源:源码 → 预处理、编译、汇编、链接 → 生成可执行文件、库文件、配置文件、文档等,打包在一起,就是**软件包**。

1. 源码安装方式:
源码(`.c`)→ `gcc` 四步(预处理→编译→汇编→链接)→ 得到可执行程序,然后手动运行。
缺点:麻烦,还要自己解决依赖(这个软件依赖别的库,你得自己先装)。
2. 软件包是什么:
别人提前把源码编译完,把**可执行程序 + 依赖信息 + 配置文件 + 说明文档**打包成一个包(rpm /deb)。

- CentOS 系:`.rpm` 包,包管理器 `yum`
- Ubuntu 系:`.deb` 包,包管理器 `apt`
>类比:
> 源码 = 食材菜谱,你自己买菜炒菜(gcc 编译)
> 软件包 = 外卖套餐,商家已经做好饭菜(编译好的可执行文件),附带餐具(配置、文档)
> 包管理器 (yum/apt) = 外卖平台,自动帮你把配套的配菜(依赖库)一起下单装好

3. 重点区分一句话:

- 软件包里面**存放的是编译好的二进制**,不是源码(源码包是另外一回事,叫 src 包)
- 安装软件包的时候:**不再执行 gcc 编译**,只是把里面的可执行文件、库拷贝到系统对应目录,注册依赖。

## 补充两个容易混淆点

1. 源码包(src.rpm/source deb):里面放的是原始 C 源码,拿到后依然要自己 gcc 编译。我们 yum/apt 默认装的是**二进制包**。
2. 依赖关系:比如装一个图形工具,它依赖 libc、libpng 这些库,yum/apt 会自动查出来,一并下载安装,这就是包管理器最大的价值。

1-2 Linux软件⽣态 

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

•

这张图非常直观,展示 **yum/apt 包管理器的工作流程**:

1. 左边:你的云服务器上运行 `yum` / `apt`(包管理器程序)
它的核心能力:**自动解决依赖、下载、安装、卸载软件**
2. 右边:远端软件包服务器(源服务器,阿里云 / 清华源就是这种服务器),上面存放一堆提前编译好的软件包
3. 箭头流程:
   - 你敲安装命令 → 包管理器向源服务器发送**查找、下载请求**
   - 服务器把对应的软件包(以及它所有依赖的包)**返回下载**到本地
   - 包管理器再把包里面的二进制程序、库、配置文件拷贝到系统对应目录,完成安装

把 **ELF 可执行文件 + 配置 + 依赖信息 + 库** 一起打包成 rpm/deb 软件包。

操作系统的好坏评估--- ⽣态问题

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

软件包依赖的问题

国内镜像源

以下是⼀些国内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发⾏版的镜像,以及相关的软件仓库和⼯具。该镜像站还
提供了便捷的搜索功能,帮助⽤⼾快速找到所需的软件包。
此外,还有⼀些其他的国内镜像源,如搜狐开源镜像站等,但可能由于时间变化或政策调 整,部分镜像站的链接或状态可能有所变动。因此,建议⽤⼾在使⽤前访问官⽅⽹站或咨询 相关社区以获取最新的信息和帮助

从右往左是**软件包的生产链路**,从左往右是**我们使用包管理器的链路**

1. 右侧:开发者写源代码 → 在对应系统环境下编译发布 → 生成 CentOS(rpm)、Ubuntu(deb)等二进制软件包
> 
> 这里编译发布的时候,开发者已经完成:`.c → 预处理→编译→汇编→链接`,产出最终 ELF 可执行程序,**不是.o 文件**,打包成软件包
2. 然后软件包上传上线,放到服务器上,分成两类源:
   - **稳定软件源**:官方维护,软件版本稳定、兼容性好
   - **扩展软件源**:放一些额外、新一点的软件
3. 中间大框:原始是国外软件包服务器
4. 左边:你的云服务器上 `yum/apt`(包管理器),读取配置文件(国内.conf)
✅ **换源的本质就在这里**:把配置指向国内镜像服务器(阿里云 / 清华源),而不是直接访问国外服务器
包管理器发起查找、下载请求 → 服务器返回软件包 → 包管理器自动处理依赖、安装、卸载

镜像源也是为了提高我们的访问速度

1-3 yum具体操作

1-3-1 查看软件包 

通过 yum list 命令可以罗列出当前⼀共有哪些软件包. 由于包的数⽬可能⾮常之多, 这⾥我们需要使⽤ grep 命令只筛选出我们关注的包. 例如: 

 Centos
$ yum list | grep lrzsz
lrzsz.x86_64 0.12.20-36.el7 @base
# Ubuntu
$ apt search lrzsz
Sorting... Done
Full Text Search... Done
cutecom/focal 0.30.3-1build1 amd64
Graphical serial terminal, like minicom
lrzsz/focal,now 0.12.21-10 amd64 [installed]
Tools for zmodem/xmodem/ymodem file transfer
$ apt show lrzsz
Package: lrzsz

16Version: 0.12.21-10
Priority: optional
Section: universe/comm
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Martin A. Godisch <godisch@debian.org>

注意事项: 
• 软件包名称: 主版本号.次版本号.源程序发⾏号-软件包的发⾏号.主机平台.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" 确认安装.  
• 出现 "complete" 字样或者中间未出现报错, 说明安装完成. 
注意事项: 
• 安装软件时由于需要向系统⽬录中写⼊内容, ⼀般需要 sudo 或者切到 root 账⼾下才能完成. 
• yum/apt安装软件只能⼀个装完了再装另⼀个. 正在yum/apt安装⼀个软件的过程中, 如果再尝试⽤
yum/apt安装另外⼀个软件, yum/apt会报错. 
• 如果 yum / apt报错, 请⾃⾏百度.

 # Centossudo 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/ # 扩展源
# ubuntu 安装扩展源,就结合未来具体场景

## 1. CentOS(yum)

路径:`/etc/yum.repos.d/`
里面后缀是 `.repo` 的文件,每一个文件就是一个源的配置:

- `CentOS-Base.repo`:**基础稳定源**,系统自带核心软件
- `epel.repo`:EPEL 扩展源,很多官方源没有的软件在这里
- 命令 `sudo yum install -y epel-release`:用来安装 epel 扩展源,自动生成 epel.repo

> 
> 换阿里云 / 清华源,本质就是:**把这个目录里的 CentOS-Base.repo 替换成国内镜像版本的 repo 文件**

## 2. Ubuntu(apt)

- 主源配置:`/etc/apt/sources.list` (单个文件,存放标准软件源地址)
- 扩展源目录:`/etc/apt/sources.list.d/` (放额外第三方源,一个软件一个 list 文件)

> 
> Ubuntu 换源,就是修改 `sources.list` 里面的网址,换成清华 / 阿里云镜像地址。

更新安装源,现场说明原理即可。云服务器不⽤考虑,因为软件源都是国内的了. 好玩的命令:https://blog.csdn.net/xiaoxiaosutang/article/details/141053674 

这个路径里面存放的是记录软件源服务器地址的配置文件,yum/apt 读取这些地址,再去网上对应的服务器下载软件包。

2. 编辑器Vim 

 IDE例⼦ 

2-1 Linux编辑器-vim使⽤ 


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

2-2 vim的基本概念 


课堂上我们讲解vim的三种模式(其实有好多模式,⽬前掌握这3种即可),分别是命令模式(command 
mode)、插⼊模式(Insert mode)和底⾏模式(last line mode),各模式的功能区分如下: 
• 正常/普通/命令模式(Normal mode) 
控制屏幕光标的移动,字符、字或⾏的删除,移动复制某区段及进⼊Insert mode下,或者到 last line mode 
• 插⼊模式(Insert mode) 
只有在Insert mode下,才可以做⽂字输⼊,按「ESC」键可回到命令⾏模式。该模式是我们后⾯⽤ 的最频繁的编辑模式。
• 末⾏模式(last line mode) 
⽂件保存或退出,也可以进⾏⽂件替换,找字符串,列出⾏号等操作。
在命令模式下,*shift+:* 即可进⼊该模式。要查看你的所有模式:打开 vim,底⾏模式直接输⼊ :help vim-modes 
我这⾥⼀共有12种模式:six BASIC modes和six ADDITIONAL modes.这个是最常用的三种模式之间切换的基本指令

2-3 vim的基本操作 


• 进⼊vim,在系统提⽰符号输⼊vim及⽂件名称后,就进⼊vim全屏幕编辑画⾯: 
• $ vim test.c
• 不过有⼀点要特别注意,就是你进⼊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」,分别控制光标左、下、上、右移⼀格 
• 按「G」:移动到⽂章的最后 
• 按「 $ 」:移动到光标所在⾏的“⾏尾”
• 按「^」:移动到光标所在⾏的“⾏⾸” 
• 按「w」:光标跳到下个字的开头 
• 按「e」:光标跳到下个字的字尾 
• 按「b」:光标回到上个字的开头 
• 按「#l」:光标移到该⾏的第#个位置,如:5l,56l 
• 按[gg]:进⼊到⽂本开始 
• 按[shift+g]:进⼊⽂本末端 
• 按「ctrl」+「b」:屏幕往“后”移动⼀⻚ 
• 按「ctrl」+「f」:屏幕往“前”移动⼀⻚ 
• 按「ctrl」+「u」:屏幕往“后”移动半⻚ 
• 按「ctrl」+「d」:屏幕往“前”移动半⻚ 
• 删除⽂字
• 「x」:每按⼀次,删除光标所在位置的⼀个字符 • 「#x」:例如,「6x」表⽰删除光标所在位置的“后⾯(包含⾃⼰在内)”6个字符 
• 「X」:⼤写的X,每按⼀次,删除光标所在位置的“前⾯”⼀个字符 
• 「#X」:例如,「20X」表⽰删除光标所在位置的“前⾯”20个字符 
• 「dd」:删除光标所在⾏ 
• 「#dd」:从光标所在⾏开始删除#⾏ 
• 复制
• 「yw」:将光标所在之处到字尾的字符复制到缓冲区中。 
• 「#yw」:复制#个字到缓冲区 
• 「yy」:复制光标所在⾏到缓冲区。 
• 「#yy」:例如,「6yy」表⽰拷⻉从光标所在的该⾏“往下数”6⾏⽂字。 
• 「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与“y”有关的复制命令都必须
与“p”配合才能完成复制与粘贴功能。 
• 替换
• 「r」:替换光标所在处的字符。 
• 「R」:替换光标所到之处的字符,直到按下「ESC」键为⽌。 
• 撤销上⼀次操作
• 「u」:如果您误执⾏⼀个命令,可以⻢上按下「u」,回到上⼀个操作。按多次“u”可以执⾏
多次回复。
• 「ctrl + r」: 撤销的恢复 
• 更改
• 「cw」:更改光标所在处的字到字尾处 
• 「c#w」:例如,「c3w」表⽰更改3个字 
• 跳⾄指定的⾏
• 「ctrl」+「g」列出光标所在⾏的⾏号。 
• 「#G」:例如,「15G」,表⽰移动光标⾄⽂章的第15⾏⾏⾸。 

2-5 vim末⾏模式命令集 


在使⽤末⾏模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进⼊末⾏ 模式。
•  列出⾏号 
• 「set nu」: 输⼊「set nu」后,会在⽂件中的每⼀⾏前⾯列出⾏号。 • 跳到⽂件中的某⼀⾏• 「#」:「#」号表⽰⼀个数字,在冒号后输⼊⼀个数字,再按回⻋键就会跳到该⾏了,如输⼊数字
15,再回⻋,就会跳到⽂章的第15⾏。 
• 查找字符
• 「/关键字」: 先按「/」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可以
⼀直按「n」会往后寻找到您要的关键字为⽌。 
• 「?关键字」:先按「?」键,再输⼊您想寻找的字符,如果第⼀次找的关键字不是您想要的,可
以⼀直按「n」会往前寻找到您要的关键字为⽌。  
• 问题:∕ 和 ?查找有和区别?操作实验⼀下 
• 保存⽂件
• 「w」: 在冒号输⼊字⺟「w」就可以将⽂件保存起来 
• 离开vim 
• 「q」:按「q」就是退出,如果⽆法离开vim,可以在「q」后跟⼀个「!」强制离开vim。 
• 「wq」:⼀般建议离开时,搭配「w」⼀起使⽤,这样在退出的时候还可以保存⽂件。

2-6 vim操作总结 • 三种模式


• 正常模式
• 插⼊模式
• 底⾏模式
• 我们⼀共有12种总模式,⼤家下来可以研究⼀下 
• vim操作 
• 打开,关闭,查看,查询,插⼊,删除,替换,撤销,复制等等操作。 • 练习:当堂⼝头模式切换练习

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


配置⽂件的位置
• 在⽬录 /etc/ 下⾯,有个名为vimrc的⽂件,这是系统中公共的vim配置⽂件,对所有⽤⼾都有
效。
• ⽽在每个⽤⼾的主⽬录下,都可以⾃⼰建⽴私有的配置⽂件,命名为:“.vimrc”。例如,/root
⽬录下,通常已经存在⼀个.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. 链接(⽣成可执⾏⽂件或库⽂件) 

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  
3-2-4 连接(⽣成可执⾏⽂件或库⽂件) 
• 在成功编译之后,就进⼊了链接阶段。 
• 实例:  gcc hello.o –o hello  

我们喜欢的是先把源文件编译成对应的.o文件再通过gcc命令把所用的.o链接 形成一个可执行程序

gcc也支持命令行版的宏定义

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”函数的呢? 
• 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定
时,gcc 会到系统默认的搜索路径“/usr/lib”下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样 就能实现函数“printf”了,⽽这也就是链接的作⽤ 

3-4 静态库和动态库 


• 静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运
⾏时也就不再需要库⽂件了。其后缀名⼀般为“.a” 
• 动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由
运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为“.so”,如前⾯所述的 
libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂
件,如下所⽰。 gcc hello.o –o hello  
• gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。 
📌 注意1: 
• Linux下,动态库XXX.so, 静态库XXX.a 
• Windows下,动态库XXX.dll, 静态库XXX.lib 
📌 注意2: 
• 课堂这块需要举例说明,纯理论太⽣涩。 ⽹吧/⽹咖的例⼦ 
⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装

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

其他问题,课堂补充: • 条件编译应⽤场景
• 为什么⾮得把语⾔变成汇编
• 编译器⾃举

链接的过程就是在地址上产生联系

静态链接时直接把方法拿过来

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,多文件项目都是把.变成.o最后和库进行链接

4-3 基本使⽤ 

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

Makefile⽂件

myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc

依赖关系
• 上⾯的⽂件myproc,它依赖myproc.c 
依赖⽅法
• gcc -o myproc myproc.c  ,就是与之对应的依赖关系 
项⽬清理
• ⼯程是需要被清理的• 像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动
执⾏,不过,我们可以显⽰要make执⾏。即命令⸺“make clean”,以此来清除所有的⽬标
⽂件,以便重编译。
• 但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤ .PHONY 修饰,伪⽬标的特性
是,总是被执⾏的。
• 可以将我们的 hello ⽬标⽂件声明成伪⽬标,测试⼀下。
什么叫做总是被执⾏?

$ 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:常指的是⽂件最近⼀次被访问的时间。在Linux的早期版本中,每当⽂件被访问时,其 atime都会更新。但这种机制会导致⼤量的IO操作。具体更新原则,不做过多解释。

结论:
.PHONY:让make忽略源⽂件和可执⾏⽬标⽂件的M时间对⽐ 通过这个可以看出来不是一开始就先创建目标文件而是根据我们makefile里面的顺序来确定的所以我i们一般写makefile一般喜欢把创建写在前面

修改内容是modify被修改,修改属性是change被修改,modify以后change一般一是跟着变的,为了不出bug,linux现在在多次查看以后才会更新access时间

改过的文件和可执行文件一定有新旧所以make就在.c在可执行文件前面是就不会进行重复编译都是通过modify进行比较的

.PHONY就是忽略时间对比直接进行执行makefile遇到不认识的就会入栈,再根据最后的关系出栈

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-5 适度扩展语法 

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)

这样的变量引入就和我们c语言里面的宏相似这个现在开始就把.c文件的后缀换成了.o但是文件内部的内容并没有就行改变

5. Linux第⼀个系统程序−进度条 

5-1 补充 - 回⻋与换⾏ 

 回⻋概念
• 换⾏概念
• ⽼式打字机的例⼦
• \r&&\n 

5-2 ⾏缓冲区 

#include <stdio.h>
int main()
{
printf("hello bite!\n");
sleep(3);
return 0;
#include <stdio.h>
int main()
{
printf("hello bite!");
fflush(stdout);
sleep(3);
return 0;
}
1 2 3 4 5 6 7 

这里字符串**没有换行 `\n`**,如果没有 `fflush`:

- 内容只会留在缓冲区,不会显示,要等程序结束才刷新打印
- 加上 `fflush(stdout)`:**强制手动刷新 stdout 缓冲区**,不管有没有换行,直接把缓冲区数据刷出去
流程:

1. printf 写入缓冲区(无换行,不会自动刷)
2. `fflush(stdout)` 强制刷新 → 打印文字

没有\n就会让内容停留在缓存区,要用fflush去刷新缓存区

5-3 练⼿ - 倒计时程序 

#include <stdio.h>
#include <unistd.h>
1 2int main()
{
int i = 10;
while(i >= 0)
{
printf("%-2d\r", i); // \n
fflush(stdout);
i--;
sleep(1);
} 
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)
{
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 / 1000
int i = 0;
for(; i < num; i++)
{
buffer[i] = STYLE;
} d
ouble rate = current/total;
cnt %= len;
printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]);
cnt++;
fflush(stdout);
}

`\n` 是换行:光标跳到下一行行首;
`\r` 只是把光标挪到**本行开头**,不往下走

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>
1 2 3#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;
} p
rintf("\ndownload %.2lfMB Done\n", current);
} i
nt 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:%.c
gcc -c $<
.PHONY:
clean:
rm -f $(OBJ) $(BIN)

我们基于已经学习的 gcc、Makefile/make 工具以及 C 语言基础,完成了第一个进度条小项目。接下来,我们将学习 git、gdb、cgdb 调试工具,并正式开启 Linux 进程相关内容的讲解。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2501_94547070/article/details/169563177

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--