linux学习进展 gdb调试 及静态库共享库

张开发
2026/4/10 0:21:16 15 分钟阅读

分享文章

linux学习进展 gdb调试 及静态库共享库
上一篇笔记我们详细学习了make工具与Makefile文件的使用掌握了自动化编译的核心方法。在实际Linux开发中除了高效编译程序调试和代码复用也是不可或缺的环节——gdb调试工具能帮我们快速定位程序中的Bug静态库与共享库则能实现代码的模块化复用降低开发成本、提升项目可维护性。本次笔记将衔接上一篇的Makefile内容补充make工具的进阶用法再重点讲解gdb调试的核心操作、静态库与共享库的创建、使用及区别助力我们完成从“编译”到“调试”“复用”的完整开发流程。一、补充Makefile进阶用法衔接上一篇上一篇笔记我们学习了Makefile的基本规则、变量使用及实操案例此处补充两个高频进阶知识点进一步完善Makefile的使用能力适配中大型项目开发需求。1.1 自动生成依赖文件补充说明在多文件、多依赖的大型项目中手动添加头文件依赖繁琐且易遗漏通过以下方式可实现依赖的自动更新大幅提升开发效率-include $(DEPS)表示包含所有.d依赖文件其中$(DEPS)通常定义为所有.c文件对应的.d文件如$(SRCS:.c.d)。而gcc -MM $命令会自动分析当前.c文件$ 表示当前规则的第一个依赖文件所依赖的所有头文件并将依赖关系写入对应的.d文件中。这种方式的核心优势的是当任何头文件被修改时make工具会通过.d文件自动识别依赖变化仅重新编译受影响的源文件避免遗漏依赖导致的编译错误尤其适用于头文件数量多、依赖关系复杂的大型项目。1.2 make工具常用命令与选项掌握make的常用命令和选项可进一步提高自动化编译效率常见用法如下make默认执行Makefile中第一个规则生成第一个目标make 目标名执行指定目标如make clean、make app精准控制构建流程make -f 文件名指定要读取的Makefile文件如make -f MyMakefile适用于Makefile命名不规范的场景make -j N并行构建N为并行任务数如make -j4可同时编译多个源文件大幅加快多文件项目的编译速度注意避免并行冲突如多个规则依赖同一文件make -n模拟执行不实际运行命令仅显示要执行的步骤用于调试Makefile排查规则编写错误make -d调试模式显示详细的执行日志包括依赖检查、命令执行过程等用于排查复杂的Makefile错误make -k即使部分目标构建失败仍继续执行其他可构建目标适用于多目标项目便于一次性排查所有构建问题。1.3 Makefile常见错误与解决方法编写和使用Makefile时容易出现一些共性错误整理高频错误及解决方案帮助快速排查问题错误1missing separator缺少分隔符原因命令行没有以Tab键开头用了空格替代这是Makefile最常见的语法错误解决在编辑器中开启“显示不可见字符”确保命令行以Tab开头Vim中输入:set list可查看Tab为^I。错误2伪目标不执行原因当前目录存在与伪目标同名的文件如clean文件make默认将目标当作文件处理若文件已存在且未更新会跳过命令执行解决给伪目标添加.PHONY声明如.PHONY: clean all所有非文件目标all、install、test等都应添加此声明明确告知make这是伪目标而非实际文件。错误3变量未定义或展开错误原因变量名拼写错误如CFLAGS写成CFLAG或混淆了与:的赋值时机是递归展开:是立即展开解决检查变量名拼写优先使用:进行立即赋值可通过$(info 变量名: $(变量名))打印变量值进行调试确认变量是否正确展开。错误4依赖缺失修改头文件后未重新编译原因未将头文件列为依赖项或未使用自动生成依赖的方法解决显式添加头文件依赖或使用gcc -MM自动生成依赖文件并包含到Makefile中即1.1中补充的方法。错误5make: *** 没有指明目标并且找不到makefile原因当前目录没有Makefile/makefile文件或文件名写错如makeFile或进入了错误目录解决检查目录和文件名手动创建Makefile或通过./configure、cmake .生成Makefile再执行make命令。1.4 Makefile学习总结衔接上一篇make工具与Makefile文件的核心价值的是“自动化”和“高效化”通过定义依赖关系和构建规则减少手动编译的繁琐操作同时避免不必要的全量编译尤其适用于多文件、大型项目开发。上一篇及本次补充的Makefile重点内容需掌握Makefile的基本规则目标:依赖Tab命令牢记命令行必须用Tab开头变量的定义与使用掌握常用自动变量$、$、$^和赋值方式、:、?、简化规则编写模式规则和自动生成依赖的方法适配中大型项目避免依赖遗漏make常用命令与选项提升编译效率常见错误的排查方法避免因语法或依赖问题导致构建失败。Makefile作为Linux开发的基础工具掌握它能为后续学习Shell脚本、项目构建、嵌入式开发等内容奠定坚实基础后续可结合实际项目熟练掌握变量、函数、条件判断等高级用法。二、gdb调试工具程序调试核心在Linux C/C开发中程序运行报错如段错误、逻辑错误时仅靠printf打印日志排查问题效率极低且无法定位深层Bug如内存越界、函数调用异常。gdbGNU Debugger是Linux下一款开源的强大调试工具堪称程序调试的“瑞士军刀”它允许我们深入程序内部逐行执行代码、查看变量值、设置断点、分析内存快速定位并解决Bug。2.1 gdb安装与调试前提2.1.1 安装gdb大多数Linux发行版默认未预装gdb需通过包管理器安装不同发行版命令如下Ubuntu/Debian系列sudo apt update sudo apt install -y gdbCentOS/RHEL系列sudo yum install -y gdbCentOS 8推荐使用sudo dnf install -y gdb安装完成后执行gdb --version若输出版本信息则说明安装成功。2.1.2 调试前提生成带调试信息的可执行文件gdb调试的核心是读取程序的调试信息如行号、变量名、函数信息而gcc默认编译生成的可执行文件不包含调试信息因此必须在编译时添加-g选项生成带调试信息的可执行文件。示例单文件gcc -g main.c -o main示例结合Makefile在Makefile中添加CFLAGS -g确保所有源文件都带调试信息编译CC : gcc CFLAGS : -Wall -g # -g添加调试信息 TARGET : main SRCS : main.c utils.c OBJS : $(SRCS:.c.o) $(TARGET): $(OBJS) (CC) $(CFLAGS) $^ -o $ %.o: %.c $(CFLAGS) -c $ -o $ .PHONY: clean clean: rm -f $(OBJS) $(TARGET) $(CC) $注意若未添加-g选项启动gdb后会提示“No debugging symbols found”无法进行正常调试。2.2 gdb核心操作常用命令gdb调试的基本流程启动gdb → 设置断点 → 运行程序 → 调试查看变量、单步执行等 → 退出gdb以下是最常用的核心命令结合示例讲解易懂好记。2.2.1 启动与退出gdb启动gdbgdb 可执行文件名示例gdb main启动后进入gdb交互界面提示符为“(gdb)”退出gdb在交互界面输入quit可缩写为q即可退出gdb调试。2.2.2 断点操作调试核心断点用于让程序在指定位置停止运行方便我们查看此时的程序状态常用命令如下break 行号缩写b 行号在指定行设置断点示例b 10在第10行设置断点break 函数名缩写b 函数名在指定函数的入口处设置断点示例b main在main函数入口设置断点info breakpoints缩写info b查看所有断点信息包括断点编号、位置、状态delete 断点编号缩写d 断点编号删除指定断点示例d 1删除编号为1的断点disable 断点编号禁用断点不删除后续可启用enable 断点编号启用被禁用的断点。2.2.3 程序运行控制设置断点后通过以下命令控制程序运行逐步排查问题run缩写r运行程序直到遇到断点或程序结束next缩写n单步执行跳过函数调用不进入函数内部适合快速排查代码流程step缩写s单步执行进入函数内部适合调试函数内部逻辑continue缩写c继续运行程序直到下一个断点或程序结束finish执行完当前函数返回到调用函数的位置until 行号缩写u 行号快速运行到指定行跳过中间代码适合快速定位到目标代码段。2.2.4 查看程序状态变量、内存、代码程序停止运行后通过以下命令查看变量值、内存状态和代码定位Bugprint 变量名缩写p 变量名查看指定变量的值示例p a查看变量a的值print 变量名查看变量的内存地址示例pa查看变量a的地址whatis 变量名查看变量的类型示例whatis a查看变量a的类型如int、char等list缩写l查看当前程序代码默认显示10行连续输入l可继续查看后续代码list 行号查看指定行附近的代码示例l 10查看第10行附近的代码x /nxf 内存地址查看指定内存地址的内容n为查看数量x为格式x十六进制、d十进制、c字符示例x /10xw 0x7fffffffdf20以十六进制、word格式查看该地址开始的10个内存单元。2.3 gdb调试实操示例假设我们有一个main.c文件存在逻辑错误计算结果异常通过gdb调试定位问题#include stdio.h int add(int a, int b) { return a - b; // 逻辑错误应为a b } int main() { int x 5, y 3; int res add(x, y); printf(5 3 %d\n, res); // 预期输出8实际输出2 return 0; }调试步骤编译带调试信息的可执行文件gcc -g main.c -o main启动gdbgdb main设置断点b add在add函数入口设断点运行程序r程序在add函数入口停止单步执行s进入add函数查看代码查看变量p a输出$1 5、p b输出$2 3继续单步执行n发现return语句是a - b定位到逻辑错误修改代码后重新编译再次调试验证问题解决退出gdbq。2.4 gdb常见问题问题1启动gdb提示“No debugging symbols found” → 解决编译时未添加-g选项重新编译并添加-g问题2断点设置成功但程序运行未停止 → 解决程序未执行到断点所在的代码如断点在未执行的分支中检查代码逻辑问题3查看变量提示“No symbol xxx in current context” → 解决变量未定义或程序未运行到变量所在的代码段先通过r或c让程序运行到变量定义后再查看。三、静态库与共享库代码复用核心在大型项目开发中很多功能模块如工具函数、常用算法会被多个文件或多个项目复用将这些可复用的代码打包成“库”既能减少代码冗余又能提高开发效率、便于维护。Linux下的库分为两种静态库.a后缀和共享库.so后缀两者的核心区别在于链接时机和代码复用方式下面详细讲解它们的创建、使用及区别。3.1 静态库Static Library静态库是将可复用的目标文件.o打包成的归档文件后缀为.a命名规则为libxxx.alib前缀库名 .a后缀。其核心特点是编译链接时静态库中的代码会被完整复制到可执行文件中生成的可执行文件独立完整运行时不依赖静态库文件即使删除静态库程序也能正常运行。3.1.1 静态库的创建三步法假设我们有两个可复用的源文件utils.c工具函数和utils.h头文件创建静态库的步骤如下// utils.h头文件声明函数 #ifndef UTILS_H #define UTILS_H int add(int a, int b); // 加法函数 int sub(int a, int b); // 减法函数 #endif // utils.c源文件实现函数 #include utils.h int add(int a, int b) { return a b; } int sub(int a, int b) { return a - b; }创建步骤编译源文件生成目标文件.ogcc -c utils.c -o utils.o-c表示只编译不链接生成目标文件打包目标文件生成静态库使用ar命令归档工具打包命令为ar rcs libutils.a utils.or替换或添加文件到库中c创建库不显示提示信息s创建索引加快链接速度。查看静态库内容可选ar -t libutils.a可查看静态库中包含的目标文件。执行完成后当前目录会生成libutils.a静态库文件。3.1.2 静态库的使用假设我们有一个main.c文件需要调用静态库中的add和sub函数使用步骤如下// main.c #include stdio.h #include utils.h // 包含静态库的头文件 int main() { printf(3 5 %d\n, add(3, 5)); printf(10 - 4 %d\n, sub(10, 4)); return 0; }编译链接命令gcc main.c -o main -L. -lutils参数说明-L.指定静态库的搜索路径为当前目录.表示当前目录-lutils指定要链接的静态库l后面跟库名去掉lib前缀和.a后缀即utils。编译完成后生成main可执行文件运行./main即可正常输出结果。此时即使删除静态库libutils.amain程序仍能正常运行因为静态库的代码已被复制到可执行文件中。3.2 共享库Shared Library共享库也称为动态库后缀为.so命名规则为libxxx.solib前缀库名 .so后缀。其核心特点是编译链接时仅将共享库的引用符号信息写入可执行文件不复制库代码程序运行时操作系统才会将共享库加载到内存中供程序调用。多个程序可共享同一份共享库节省磁盘和内存空间。3.2.1 共享库的创建三步法仍使用utils.c和utils.h文件创建共享库的步骤如下编译源文件生成位置无关目标文件.ogcc -c -fPIC utils.c -o utils.o-fPIC生成位置无关代码这是共享库必需的确保共享库可被多个程序加载到不同内存地址使用。链接目标文件生成共享库gcc -shared -o libutils.so utils.o-shared表示生成共享库。可选一步到位生成共享库gcc -shared -fPIC -o libutils.so utils.c跳过单独生成.o文件的步骤。执行完成后当前目录会生成libutils.so共享库文件。3.2.2 共享库的使用共享库的使用方式与静态库类似仍使用main.c文件编译链接命令相同gcc main.c -o main -L. -lutils但注意共享库的运行依赖库文件本身直接运行./main会报错系统默认只在/lib、/usr/lib等系统路径下查找共享库不会搜索当前目录需通过以下方式解决方法1设置环境变量LD_LIBRARY_PATH指定共享库搜索路径临时生效重启终端失效export LD_LIBRARY_PATH./指定当前目录为共享库搜索路径方法2将共享库复制到系统标准路径永久生效sudo cp libutils.so /usr/lib/方法3编译时指定共享库的绝对路径示例gcc main.c -o main /home/user/test/libutils.so。解决后运行./main即可正常输出结果。此时若删除共享库libutils.so程序会无法运行提示“找不到共享库”。3.3 静态库与共享库的核心区别静态库和共享库的核心区别在于链接时机、代码复用方式和运行依赖整理对比表如下清晰区分两者差异对比维度静态库.a共享库.so链接时机编译链接时程序运行时代码复用方式代码被完整复制到可执行文件多程序使用会产生冗余拷贝代码独立存在多程序共享同一份库文件无冗余可执行文件大小较大包含库代码较小仅包含库引用运行依赖无运行时不依赖静态库有运行时必须存在共享库更新维护更新库后所有依赖它的程序需重新编译链接更新库后接口兼容无需重新编译程序替换库文件即可启动速度快无运行时加载开销相对慢需运行时加载并解析库符号适用场景小型工具、嵌入式设备、需独立运行、不常更新的程序大型项目、系统级库、多程序共享、需频繁更新的程序3.4 常见问题与注意事项问题1链接库时提示“undefined reference to xxx” → 解决检查库名是否正确-l后面跟库名去掉lib前缀和后缀、库路径是否正确-L指定路径或库中是否包含该函数问题2运行共享库程序时提示“cannot open shared object file” → 解决通过设置LD_LIBRARY_PATH、复制库到系统路径或指定绝对路径让系统能找到共享库注意静态库和共享库同名时如同时存在libutils.a和libutils.sogcc默认优先链接共享库若需强制链接静态库需添加-static选项示例gcc main.c -o main -L. -static -lutils注意创建共享库时必须添加-fPIC参数否则会报错无法生成位置无关代码。四、学习总结本次笔记衔接上一篇Makefile的内容补充了Makefile的进阶用法、常用命令及错误排查重点学习了gdb调试工具和静态库、共享库的核心知识三者共同构成了Linux C/C开发的“编译-调试-复用”完整流程核心要点总结如下Makefile进阶掌握自动生成依赖的方法-include $(DEPS)和gcc -MM $熟悉make常用命令与选项能快速排查Makefile常见错误gdb调试牢记“编译加-g”的调试前提掌握启动/退出、断点设置、程序控制、状态查看的核心命令能快速定位程序Bug静态库与共享库掌握两者的创建、使用方法明确核心区别能根据项目需求选择合适的库类型——需独立运行、不常更新选静态库需多程序共享、频繁更新选共享库。后续学习中可结合实际项目将Makefile、gdb、静态库/共享库结合使用比如用Makefile实现库的自动化构建和程序编译用gdb调试库和程序的交互问题逐步提升Linux开发的效率和规范性。这三个工具是Linux开发的基础掌握它们能为后续学习更复杂的项目构建、嵌入式开发、系统编程等内容奠定坚实基础。

更多文章