一、软件包管理器
在 Linux 系统中,软件包管理器扮演着类似“应用商店”的角色,它极大地简化了软件的安装、升级和卸载过程。
1. 什么是软件包与软件包管理器?
传统上,在 Linux 中安装软件需要下载源代码,然后手动编译生成可执行程序,这个过程相对繁琐。为了简化这一流程,开发者们将常用的软件预先编译好,制作成软件包(类似于 Windows 上的安装程序),并存放在服务器上。用户可以通过软件包管理器方便地获取这些预编译的软件包并直接安装。
- yum (Yellow dog Updater, Modified):主要应用于 Fedora、RedHat、CentOS 等发行版。
- apt (Advanced Package Tool):是 Ubuntu 等 Debian 系发行版主要的软件包管理器。
这两种工具都能自动处理软件包之间的依赖关系,实现便捷的下载和安装。
2. Linux 软件生态与依赖关系
操作系统的优劣很大程度上取决于其软件生态的健康程度。 这包括社区论坛的活跃度、官方文档的完善性、软件体系的丰富性、维护更新的速度以及是否有针对性的用户群体等。
当您通过 yum
或 apt
请求安装一个软件时,包管理器会首先查找该软件包,如果找到,则从软件包服务器下载并返回给您的云服务器进行安装、卸载或依赖关系解析。 很多软件的运行依赖于其他库文件(例如 libc.so
, ssl.so
, libhttplib.so
等),这就是所谓的软件包依赖。包管理器会自动处理这些依赖,确保所有必需的组件都被正确安装。
3. 国内镜像源的重要性
由于网络原因,直接从国外的官方软件包服务器下载软件可能会比较慢。因此,使用国内的镜像源可以显著提高下载速度和稳定性。
通常,云服务器已经配置了国内的软件源,个人用户可以根据需求配置或更新安装源。 此外,除了发行版自带的稳定软件源,有时我们还需要扩展软件源(如 EPEL for CentOS)来获取更多的软件包。
4. yum/apt
常用操作
-
查看软件包:
- CentOS:
yum list | grep <软件包名>
- Ubuntu:
apt search <软件包名>
或apt show <软件包名>
软件包信息通常包含名称、主版本号、次版本号、源程序发行号、软件包发行号、主机平台(如 x86_64 代表64位,i686 代表32位)以及操作系统发行版版本(如 el7 代表 CentOS 7/RedHat 7)。 最后一列通常表示软件源的名称。
- CentOS:
-
安装软件:
- CentOS:
sudo yum install -y <软件包名>
- Ubuntu:
sudo apt install -y <软件包名>
安装软件时通常需要sudo
权限,因为需要向系统目录写入内容。 安装过程中,如果再次尝试安装其他软件,yum/apt
可能会报错。
- CentOS:
-
卸载软件:
- CentOS:
sudo yum remove [-y] <软件包名>
- Ubuntu:
sudo apt remove [-y] <软件包名>
- CentOS:
-
注意事项:所有
yum/apt
操作都必须保证主机网络畅通,可以通过ping www.baidu.com
来验证。 -
安装源配置文件路径:
- CentOS 标准源:
/etc/yum.repos.d/CentOS-Base.repo
;扩展源(如 epel.repo)也在此目录。 - Ubuntu 标准源:
/etc/apt/sources.list
;扩展源在/etc/apt/sources.list.d/
目录下。
- CentOS 标准源:
二、Vim 编辑器
Vim (Vi IMproved) 是从 vi 编辑器发展出来的一个功能非常强大的文本编辑器。它以其多模式操作和高度可配置性而闻名。
1. Vim 的基本概念:模式
Vim 的核心特性之一就是它的多种操作模式。初学者需要重点掌握以下三种模式:
- 正常/普通/命令模式 (Normal mode):进入 Vim 后的默认模式。在此模式下,可以移动光标、删除字符/行、复制粘贴文本,以及切换到其他模式。
- 插入模式 (Insert mode):在此模式下,可以进行文本输入。 按
ESC
键可以从插入模式返回到正常模式。 - 末行模式 (Last line mode):在此模式下,可以执行保存文件、退出 Vim、查找替换、显示行号等操作。 在正常模式下输入
:
(shift + 😉 即可进入末行模式。
2. Vim 的基本操作
- 进入 Vim:在终端输入
vim <文件名>
(例如vim test.c
) 即可打开或创建一个文件并进入 Vim 的正常模式。 - 模式切换:
- 从正常模式到插入模式:
i
:在光标当前位置前插入。a
:在光标当前位置后追加。o
:在当前行的下方插入一个新行,并将光标置于行首。
- 从插入模式到正常模式:按
ESC
键。 - 从正常模式到末行模式:输入
:
。
- 从正常模式到插入模式:
- 退出 Vim 及保存文件 (在末行模式下):
:w
:保存当前文件。:wq
:保存当前文件并退出 Vim。:q!
:不保存文件并强制退出 Vim。
3. Vim 正常模式常用命令
- 移动光标:
h
,j
,k
,l
:分别向左、下、上、右移动一格。G
:移动到文件的最后一行。gg
:移动到文件的第一行。$
:移动到当前行的行尾。^
:移动到当前行的行首。w
:光标跳到下一个单词的开头。e
:光标跳到下一个单词的词尾。b
:光标回到上一个单词的开头。#l
:光标移到该行的第#个位置 (例如5l
)。ctrl + f
/ctrl + b
:向前/向后翻一页。ctrl + d
/ctrl + u
:向前/向后翻半页。
- 删除文字:
x
:删除光标所在位置的一个字符。#x
:删除光标所在位置(包含自身)后的 # 个字符 (例如6x
)。X
:删除光标所在位置前的一个字符。#X
:删除光标所在位置前的 # 个字符 (例如20X
)。dd
:删除光标所在的行。#dd
:从光标所在行开始删除 # 行。
- 复制与粘贴:
yw
:复制从光标处到词尾的字符。#yw
:复制 # 个单词。yy
:复制光标所在的行。#yy
:复制从光标所在行开始的 # 行 (例如6yy
)。p
:将缓冲区中的内容粘贴到光标所在位置之后。
- 替换:
r
:替换光标所在处的单个字符。R
:进入替换模式,持续替换字符,直到按下ESC
。
- 撤销与重做:
u
:撤销上一次操作。ctrl + r
:重做被撤销的操作。
- 更改:
cw
:更改从光标处到词尾的字符(删除并进入插入模式)。c#w
:更改 # 个单词。
- 跳转至指定行:
ctrl + g
:显示当前光标所在行的行号及文件信息。#G
:跳转到文件的第 # 行 (例如15G
)。
4. Vim 末行模式常用命令
- 列出行号:
:set nu
。 - 跳到文件中的某一行:
:<行号>
(例如:15
)。 - 查找字符:
/<关键字>
:向下查找关键字,按n
查找下一个。?<关键字>
:向上查找关键字,按n
查找下一个。
- 保存文件:
:w
。 - 离开 Vim:
:q
(如果文件已修改但未保存,会提示;可使用:q!
强制退出)。:wq
保存并退出。
5. 简单 Vim 配置 [了解]
Vim 的配置文件可以实现个性化设置,提高编辑效率。
- 配置文件位置:
- 系统级公共配置:
/etc/vimrc
(对所有用户有效)。 - 用户个人配置:
~/.vimrc
(在用户主目录下,如果不存在则需创建)。
- 系统级公共配置:
- 常用配置选项示例 (在
.vimrc
文件中添加):- 设置语法高亮:
syntax on
- 显示行号:
set nu
- 设置缩进空格数为4:
set shiftwidth=4
- 设置语法高亮:
- 使用插件:可以通过安装插件来增强 Vim 的功能,例如 TagList (显示代码结构)、WinManager (文件浏览器和窗口管理器)。 安装插件通常涉及下载插件包,解压并将其中的
doc
和plugin
目录内容分别放到~/.vim/doc
和~/.vim/plugin
目录下,然后在.vimrc
中添加相应配置。
三、编译器 GCC/G++
GCC (GNU Compiler Collection) 和 G++ 是 Linux 下最常用的 C 和 C++ 编译器。它们负责将我们编写的源代码转换成计算机可以执行的机器代码。
1. 编译过程
一个 C/C++ 程序的编译通常经历以下四个主要阶段:
- 预处理 (Preprocessing):处理以
#
开头的预处理指令,例如宏定义 (#define
)、文件包含 (#include
)、条件编译 (#if
,#else
,#endif
) 以及去除注释。- 命令示例:
gcc –E hello.c –o hello.i
(选项-E
让 gcc 在预处理结束后停止,-o
指定输出文件名为hello.i
)。
- 命令示例:
- 编译 (Compilation):检查代码的规范性和语法错误,然后将预处理后的代码翻译成汇编语言。
- 命令示例:
gcc –S hello.i –o hello.s
(选项-S
只进行编译而不进行汇编,生成汇编代码文件hello.s
)。
- 命令示例:
- 汇编 (Assembly):将汇编代码转换成机器可识别的二进制目标代码 (通常是
.o
文件)。- 命令示例:
gcc –c hello.s –o hello.o
(选项-c
进行汇编,生成目标文件hello.o
)。
- 命令示例:
- 链接 (Linking):将一个或多个目标文件以及它们所需要的库文件链接起来,生成最终的可执行文件或库文件。
- 命令示例:
gcc hello.o –o hello
(将hello.o
链接生成可执行文件hello
)。
- 命令示例:
2. 动态链接与静态链接
当项目包含多个源文件时,每个源文件会独立编译成目标文件 (.o
文件)。为了让这些目标文件能够协同工作(例如一个文件调用另一个文件中定义的函数),就需要将它们链接起来。
-
静态链接:在链接阶段,将所有需要的库文件的代码完整地复制到最终生成的可执行文件中。
- 优点:可执行文件包含了所有依赖,运行时不依赖外部库,执行速度可能更快。
- 缺点:浪费空间(多个程序可能都包含同一库的副本),更新困难(库函数修改后,所有依赖它的程序都需要重新编译链接)。
- 静态库在 Linux 下通常以
.a
为后缀,Windows 下为.lib
。
-
动态链接:在链接阶段,并不会把库文件的代码复制到可执行文件中,而是在程序运行时才由系统加载所需的动态库。
- 优点:节省空间(多个程序共享同一份库),更新方便(库更新后,依赖它的程序无需重新编译即可使用新版库)。
- 缺点:运行时依赖外部库文件。
- 动态库在 Linux 下通常以
.so
(Shared Object) 为后缀 (例如libc.so.6
),Windows 下为.dll
。 - GCC 默认使用动态链接。可以使用
ldd <可执行文件名>
命令查看程序依赖的共享库列表。
3. 库的概念
我们常用的函数如 printf
的实现并不在我们的 C 代码或头文件 stdio.h
中,而是存在于系统中预编译好的库文件中,如 libc.so.6
。链接器会在编译时或运行时将我们的程序与这些库连接起来,使得程序能够调用库中定义的函数。 GCC 默认会在系统指定的路径 (如 /usr/lib
) 查找所需的库文件。 通常云服务器可能没有安装 C/C++ 的静态库,可以使用如 yum install glibc-static libstdc++-static -y
(CentOS) 来安装。
4. GCC 其他常用选项 [了解即可]
-E
: 只进行预处理,不生成文件,通常需要重定向输出。-S
: 编译到汇编语言,不进行汇编和链接。-c
: 编译到目标代码。-o <文件名>
: 指定输出文件名。-static
: 使用静态链接。-g
: 生成调试信息,供 GDB 等调试器使用。-shared
: 尽量使用动态库,生成文件较小。-O0, -O1, -O2, -O3
: 编译优化级别,-O0
无优化,-O1
为默认,-O3
优化级别最高。-w
: 不生成任何警告信息。-Wall
: 生成所有警告信息。
四、自动化构建 Make/Makefile
对于包含多个源文件的大型项目,手动逐个编译并链接是非常低效且容易出错的。Make 是一个自动化构建工具,它通过读取名为 Makefile (或 makefile) 的文件中的指令,来自动完成编译、链接等任务。
1. 为什么需要 Make/Makefile?
- 自动化编译:一个工程中的源文件可能很多,Makefile 定义了一系列规则来指定哪些文件需要先编译,哪些后编译,哪些需要重新编译,大大提高了开发效率。
- 增量编译:Make 能够判断哪些源文件被修改过,只重新编译被修改过的文件及其依赖的文件,节省编译时间。
- 工程管理能力:会不会写 Makefile,在一定程度上反映了一个人管理大型工程的能力。
2. Makefile 基本语法与理解
一个简单的 Makefile 示例如下:
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc
- 规则 (Rule):Makefile 由一系列规则组成。每条规则包含:
- 目标 (Target):通常是要生成的文件名,例如
myproc
。 冒号:
前面的部分。 - 依赖 (Prerequisites/Dependencies):构建目标所需要的文件或其他的目标,例如
myproc.c
。 冒号:
后面的部分。 - 命令 (Commands):构建目标所需要执行的 Shell 命令,例如
gcc -o myproc myproc.c
。 注意:命令前必须有一个制表符 (Tab),不能是空格。
- 目标 (Target):通常是要生成的文件名,例如
- 依赖关系:目标
myproc
依赖于源文件myproc.c
。 - 依赖方法:当
myproc.c
文件比myproc
文件新,或者myproc
文件不存在时,执行命令gcc -o myproc myproc.c
来生成myproc
。 - 项目清理 (
clean
):clean
是一个常见的伪目标,用于清除编译生成的文件。 - 伪目标 (
.PHONY
):.PHONY
用来声明一个目标是伪目标。 伪目标的特性是,无论其是否存在或者其依赖是否最新,它所定义的命令总是会被执行。clean
通常被声明为伪目标,以确保make clean
命令总是能执行清理操作,即使当前目录下碰巧存在一个名为clean
的文件。 Make 在执行时会比较目标文件和依赖文件的修改时间 (Modify time, Mtime) 来决定是否需要重新构建。.PHONY
的作用之一就是让 make 忽略这种时间比较。
3. Make 的工作流程
当在命令行输入 make
时:
- Make 会在当前目录下查找名为 “Makefile” 或 “makefile” 的文件。
- 如果找到,它会处理文件中的第一个目标作为最终目标 (例如上例中的
myproc
)。 - 如果最终目标文件不存在,或者其依赖文件 (如
myproc.o
) 的修改时间比最终目标文件新,Make 就会执行该目标对应的命令来生成它。 - 如果最终目标的依赖文件 (如
myproc.o
) 也不存在,Make 会在 Makefile 中查找以该依赖文件为目标的规则,并根据这个规则先生成该依赖文件。这个过程会递归进行,像一个堆栈操作。 - Make 会一层层地查找文件的依赖关系,直到最终编译出第一个目标文件。
- 如果在查找过程中出现错误 (例如依赖的文件找不到),或者定义的命令执行出错 (例如编译不成功),Make 通常会退出并报错。 Make 主要关注文件的依赖性和命令的正确执行。
4. Makefile 推导过程示例
一个更完整的 Makefile,展示了从 .c
到 .i
(预处理),到 .s
(汇编代码),到 .o
(目标文件),再到最终可执行文件的完整过程:
myproc:myproc.o
gcc myproc.o -o myproc
myproc.o:myproc.s
gcc -c myproc.s -o myproc.o
myproc.s:myproc.i
gcc -S myproc.i -o myproc.s
myproc.i:myproc.c
gcc -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
5. Makefile 常用扩展语法
Makefile 支持变量和一些内置函数,使得编写更灵活和通用。
BIN=proc.exe # 定义变量 BIN
CC=gcc # 定义变量 CC
SRC=$(wildcard *.c) # 使用 wildcard 函数获取当前目录下所有 .c 文件名,赋值给 SRC
# 或者使用 shell 命令: SRC=$(shell ls *.c)
OBJ=$(SRC:.c=.o) # 将 SRC 中的 .c 后缀替换为 .o,形成目标文件列表,赋值给 OBJ
LFLAGS=-o # 定义链接选项
FLAGS=-c # 定义编译选项
RM=rm -f # 定义删除命令
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^ # $@ 代表目标文件名 (proc.exe)
# $^ 代表所有依赖文件名 (所有 .o 文件)
# @ 符号表示不回显该命令本身
@echo "linking ... $^ to $@"
%.o:%.c # 模式规则:表示任何 .o 文件都依赖于同名的 .c 文件
@$(CC) $(FLAGS) $< # $< 代表第一个依赖文件名 (单个 .c 文件)
@echo "compling ... $< to $@"
.PHONY:clean test
clean:
$(RM) $(OBJ) $(BIN) # 使用变量 $(RM)
test:
@echo $(SRC)
@echo $(OBJ)
五、Linux 第一个“实用”小程序
学习了基本的编译工具后,我们可以尝试编写一些简单但有趣的小程序,例如命令行进度条。这涉及到一些控制台输出的技巧。
1. 回车与换行,以及行缓冲区的概念
- 回车 (
\r
, Carriage Return):将光标移动到当前行的开头。 - 换行 (
\n
, New Line/Line Feed):将光标移动到下一行的开头。 - 在早期的打字机上,回车和换行是两个独立的操作。 在计算机中,不同的操作系统对行尾结束符的处理可能不同 (Windows 用
\r\n
,Linux/macOS 用\n
)。 - 行缓冲区 (Line Buffering):对于标准输出 (
stdout
),通常是行缓冲的。这意味着输出的内容会先暂存在一个缓冲区中,直到遇到换行符\n
,或者缓冲区满了,或者主动刷新缓冲区 (使用fflush(stdout)
) 时,才会真正显示到屏幕上。- 如果执行
printf("hello bite!\n"); sleep(3);
,会先打印 “hello bite!” 然后等待3秒。 - 如果执行
printf("hello bite!"); sleep(3);
,可能会先等待3秒,然后程序结束时缓冲区被刷新,“hello bite!” 才显示出来 (或者根本不显示,取决于具体实现)。 - 如果执行
printf("hello bite!"); fflush(stdout); sleep(3);
,则会先打印 “hello bite!”,然后等待3秒。
- 如果执行
2. 倒计时程序示例
利用 \r
可以实现在同一行覆盖输出,从而制作倒计时效果:
#include <stdio.h>
#include <unistd.h> // for sleep/usleep
int main() {
int i = 10;
while (i >= 0) {
printf("%-2d\r", i); // %-2d 左对齐占两位,\r 使光标回到行首
fflush(stdout); // 立即刷新缓冲区
i--;
sleep(1); // 等待1秒
}
printf("\n"); // 最后换行
return 0;
}
3. 进度条代码实现
下面是一个简单的进度条实现,包含 process.h
, process.c
, main.c
和 Makefile
。
-
process.h
#pragma once #include <stdio.h> void process_v1(); void FlushProcess(double total, double current);
-
process.c
(包含两个版本的进度条函数)process_v1()
: 一个简单的自增百分比和旋转字符的进度条。#include "process.h" #include <string.h> #include <unistd.h> #define NUM 101 #define STYLE '=' // version 1 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); // 延时50毫秒 } printf("\n"); }
FlushProcess(double total, double current)
: 一个更通用的进度条函数,根据当前值和总值计算百分比。// ... (头文件和宏定义同上) ... // version 2 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_chars = (int)(current * 100 / total); // 计算填充字符的数量 if (num_chars > 100) num_chars = 100; // 确保不超过100 for (int i = 0; i < num_chars; i++) { buffer[i] = STYLE; } double rate = current / total; if (rate > 1.0) rate = 1.0; // 确保百分比不超过100% printf("[%-100s][%.1f%%][%c]\r", buffer, rate * 100, lable[cnt % len]); cnt++; fflush(stdout); }
-
main.c
(调用进度条函数模拟下载过程)#include "process.h" #include <stdio.h> #include <unistd.h> double total_size = 1024.0; // 总大小 double download_speed = 100.0; // 每步下载量 (示例中调整了速度) void DownLoad() { double current_downloaded = 0; while (current_downloaded <= total_size) { FlushProcess(total_size, current_downloaded); usleep(30000); // 模拟下载耗时 current_downloaded += download_speed; } // 确保最后显示100% FlushProcess(total_size, total_size); printf("\nDownload %.2lfMB Done\n", total_size); // 文档中此处 current 变量可能已超出 total } int main() { //process_v1(); // 可以测试版本1 DownLoad(); // 可以多次调用 DownLoad 模拟多次下载 return 0; }
-
Makefile
(用于编译进度条程序)SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o) BIN=processbar $(BIN):$(OBJ) gcc -o $@ $^ %.o:%.c gcc -c $< .PHONY:clean clean: rm -f $(OBJ) $(BIN)
六、版本控制器 Git 与代码托管平台 GitHub
在软件开发过程中,代码会不断迭代和修改。版本控制系统 (Version Control System, VCS) 能够帮助我们记录文件的每一次改动,方便回溯历史版本、比较差异以及多人协同开发。 Git 是目前最流行、最强大的分布式版本控制系统。
1. 为什么需要版本控制?
想象一下,你正在写一份重要的报告或代码项目:
- 你可能会创建多个副本,如 “报告-v1”, “报告-v2”, “报告-最终版”。
- 随着版本增多,你可能不记得每个版本具体修改了什么。
- 如果误删或改错了,想要恢复到之前的某个状态会很麻烦。
- 多人协作时,如何合并各自的修改也是一个难题。
版本控制器就是为了解决这些问题而生的,它能记录文件的历史和发展过程。
2. Git 简史
Git 由 Linux 的创始人 Linus Torvalds 在 2005 年创建,最初是为了更好地管理 Linux 内核的开发。当时,Linux 内核项目使用的商业版本控制系统 BitKeeper 收回了免费使用权,迫使社区开发自己的工具。 Git 的设计目标包括速度快、设计简单、强力支持非线性开发(分支管理)、完全分布式,并且能够高效管理超大规模项目。
3. 安装 Git
在 CentOS 上,可以使用 yum install git
命令安装 Git。 在 Ubuntu 上,则是 sudo apt install git
。
4. 在 GitHub 创建项目并克隆到本地
GitHub 是一个基于 Git 的代码托管平台,开发者可以在上面存储、分享和协作开发项目。
- 注册 GitHub 账号并创建项目:
- 访问 GitHub 官网注册账号,并进行邮箱校验。
- 登录后,点击 “New repository” (新建仓库) 按钮。
- 输入项目名称 (Repository name),系统会自动校验名称是否可用。
- 可以选择 Public (公开) 或 Private (私有) 仓库,以及添加 README 文件、.gitignore 文件 (指定忽略哪些文件) 和许可证。
- 点击 “Create repository” 按钮完成创建。
- 复制项目链接:在创建好的项目页面中,找到并复制项目的 HTTPS 或 SSH 链接,用于后续克隆。
- 克隆项目到本地:在你希望存放代码的本地目录下,执行
git clone [url]
命令,其中[url]
就是刚才复制的项目链接。 这会在本地创建一个与远程仓库同名的目录,并将仓库内容下载下来。
5. Git 基本工作流程(三板斧)
将你的代码文件(例如前面编写的进度条程序 process.c
, process.h
, main.c
, Makefile
)放到刚才克隆下来的本地项目目录中。
-
git add <文件名>
或git add .
:将文件改动添加到暂存区 (Staging Area)。git add <文件名>
:添加指定文件。git add .
:添加当前目录下所有有改动的文件 (不包括被.gitignore
忽略的文件)。
这个命令告诉 Git 你希望将这些文件的当前版本纳入下一次提交。
-
git commit -m "提交说明"
:将暂存区的内容提交到本地仓库 (Local Repository)。-m "提交说明"
:必须提供一个提交说明,简要描述本次提交的修改内容 (例如 “feat: Implement progress bar v1”)。
良好的提交说明对于代码历史的追溯非常重要。
-
git push
:将本地仓库的提交推送到远程仓库 (Remote Repository,例如 GitHub)。- 首次推送或远程仓库有更新时,可能需要输入 GitHub 的用户名和密码 (或配置 SSH 免密登录)。
- 推送成功后,刷新 GitHub 上的项目页面,就能看到你提交的代码了。
6. 其他常用 Git 命令
git status
:查看工作区和暂存区的状态,显示哪些文件被修改、哪些文件已暂存等。git log
:查看提交历史。git pull
:从远程仓库拉取最新的改动并合并到本地。.gitignore
文件:这是一个文本文件,列出了希望 Git 忽略的文件或目录模式 (例如编译生成的.o
文件、可执行文件、日志文件等)。
七、调试器 GDB/CGDB
编写的程序难免会出现错误 (Bug)。调试器 (Debugger) 是一种可以帮助我们逐步执行程序、检查变量状态、找出错误原因的工具。GDB (GNU Debugger) 是 Linux 下强大的命令行调试器。
1. 编译时开启调试信息
要使用 GDB 调试程序,必须在编译源代码时加入 -g
选项,这样生成的可执行文件中才会包含调试信息 (如变量名、行号等)。
- 不带调试信息 (release 模式,默认):
gcc mycmd.c -o mycmd
- 带调试信息 (debug 模式):
gcc mycmd.c -o mycmd -g
可以使用file mycmd
命令查看可执行文件的信息,带-g
编译的会显示 “with debug_info”。
2. GDB 常用命令
- 启动 GDB:
gdb <可执行文件名>
(例如gdb mycmd
) - 退出 GDB:
quit
或q
,或者按Ctrl + d
。
命令 (简写) | 作用 | 示例 |
---|---|---|
list (l ) | 显示源代码,从上次位置开始,每次列出10行。 | l |
list <行号> | 从指定行号开始显示源代码。 | l 15 |
list <函数名> | 列出指定函数的源代码。 | l main |
list <文件名>:<行号> | 列出指定文件的源代码。 | l mycmd.c:1 |
run (r ) | 从程序开始连续执行,直到遇到断点或程序结束。 | r |
next (n ) | 单步执行,遇到函数调用时,不进入函数内部 (视为一步执行完)。 | n |
step (s ) | 单步执行,遇到函数调用时,进入函数内部。 | s |
break <行号> (b <行号> ) | 在指定行号设置断点。 | b 10 |
break <文件名>:<行号> | 在指定文件的指定行号设置断点。 | b mycmd.c:10 |
break <函数名> | 在函数开头设置断点。 | b Sum |
info break (i b ) | 查看当前所有断点的信息。 | i b |
delete <断点编号> (d <编号> ) | 删除指定编号的断点。 | d 1 |
delete breakpoints | 删除所有断点。 | delete breakpoints |
disable <断点编号> | 禁用指定编号的断点。 | disable 1 |
enable <断点编号> | 启用指定编号的断点。 | enable 1 |
continue (c ) | 从当前位置开始连续执行程序,直到下一个断点或程序结束。 | c |
print <表达式> (p <表达式> ) | 打印表达式的值。 | p i , p start+end |
set var <变量名>=<值> | 修改变量的值 (在程序暂停时)。 | set var i=10 |
finish | 执行到当前函数返回,然后停止。 | finish |
watch <表达式> | 监视一个表达式的值,当其值改变时程序暂停 (硬件观察点)。 | watch result |
display <变量名> | 跟踪显示指定变量的值 (每次程序停止时都会显示)。 | display i |
undisplay <编号> | 取消对指定编号的变量的跟踪显示。 | undisplay 1 |
until <行号> | 执行到指定行号 (通常用于跳出循环)。 | until 20 |
backtrace (bt ) | 查看当前执行栈的各级函数调用及参数。 | bt |
info locals (i lo ) | 查看当前栈帧的局部变量值。 | i lo |
condition <断点编号> <条件> | 给已存在的断点设置条件,仅当条件满足时断点才生效。 | condition 2 i == 30 |
break ... if <条件> | 设置条件断点 (新增断点时直接加条件)。 | b 9 if i == 30 |
3. GDB 调试技巧
-
watch
监视变量变化:如果你怀疑某个变量在不应该改变的时候被改变了,可以使用watch <变量名>
。当该变量的值发生变化时,GDB 会暂停并通知你。
例如,在调试Sum
函数时,可以watch result
来观察result
累加的过程。 -
set var
修改变量值定位问题:当怀疑某个变量的初始值或某个中间值导致了问题时,可以在程序暂停时使用set var <变量名>=<新值>
来修改它,然后继续执行,观察程序的行为是否符合预期,从而帮助定位问题。
例如,如果Sum
函数返回的结果总是0,而你怀疑是某个全局标志位flag
(假设flag
初始为0,而期望是1)导致的,可以在Sum
函数内部result
计算完毕后,p flag
查看其值,然后set var flag=1
,再继续执行看最终结果是否正确。 -
条件断点 (Conditional Breakpoints):当需要在满足特定条件时才中断程序执行时(例如循环变量达到某个值),可以使用条件断点。
- 新增条件断点:
break <位置> if <条件>
,例如b 9 if i == 30
(在第9行设置断点,仅当变量i
等于30时生效)。 - 给已有断点追加条件:先用
info b
查看断点编号,然后condition <断点编号> <条件>
,例如condition 2 i == 30
(给2号断点追加条件i == 30
)。
4.cgdb
:更友好的 GDB 界面
- 新增条件断点:
cgdb
是 GDB 的一个基于文本界面的封装,它可以在调试时分屏显示源代码,使得代码跟踪更为直观。
- 安装:
- Ubuntu:
sudo apt-get install -y cgdb
- CentOS:
sudo yum install -y cgdb
[
- Ubuntu:
- 使用方法与 GDB 类似,启动时用
cgdb <可执行文件名>
。