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

【把Linux“聊”明白】编译器gcc/g++与调试器gdb/cgdb:从编译原理到高效调试

在这里插入图片描述

编译器gcc/g++与调试器gdb/cgdb

友情专栏:【把Linux“聊”明白】


文章目录

  • 编译器gcc/g++与调试器gdb/cgdb
  • 前言:
  • 一、编译器gcc/g++
    • 1-1 gcc编译选项
      • 1-1-1 预处理(进行宏替换)
      • **3-2-2 编译(生成汇编)**
      • **3-2-3 汇编(生成机器可识别代码)**
      • **3-2-4 连接(生成可执行文件或库文件)**
    • 1-2 动态链接和静态链接
      • 1-2-1 静态链接
      • 1-2-2 动态链接
      • 1-2-3 库的概念
    • 1-3 静态库和动态库
      • 1-3-1 静态库和动态库的介绍
      • 1-3-2 静态库与动态库的区别
      • 1-3-3 理论验证
    • 1-4 gcc其它常用选项
  • 二、调试器gdb/cgdb
    • 2-1 预备知识与准备工作
    • 2-2 常见调试命令
    • 2-3 常见调试技巧
  • 总结


前言:

在Linux环境下进行C/C++开发,掌握编译器gcc/g++和调试器gdb/cgdb的使用是每个开发者必备的核心技能。本文将系统性地讲解从源代码到可执行程序的完整编译流程,以及如何利用调试工具快速定位和修复代码问题。

在学习gcc/g++的使用之前,需要我们对于C/C++程序从源代码到可执行程序这一过程,即预编译、编译、汇编与链接有所了解,可参考:《C/C++编译与链接详解》

本文将分为两大部分:第一部分详细解析gcc/g++编译器的各项功能和使用技巧,包括编译选项、动静态链接机制以及库文件的处理;第二部分深入探讨gdb/cgdb调试器的实战应用,从基础调试命令到高级调试技巧,帮助读者构建完整的程序调试能力体系。


一、编译器gcc/g++

1-1 gcc编译选项

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

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

  • 预处理功能主要包括宏定义、文件包含、条件编译、去注释等。

  • 预处理指令是以 # 开头的代码行。

  • 实例:

    gcc -E test.c -o test.i
    
  • 选项 -E:该选项的作用是让 gcc 在预处理结束后停止编译过程。

  • 选项 -o:是指目标文件,“.i” 文件为已经过预处理的 C 源始程序。


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

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

  • 在检查无误后,gcc 把代码翻译成汇编语言。

  • 我们可以使用 -S 选项来查看,该选项只进行编译不进行汇编,生成汇编代码。

  • 实例:

    gcc -S test.i -o test.s
    

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

  • 汇编阶段是把编译阶段生成的 “.s” 文件转成目标文件。

  • 我们可使用选项 -c 就可看到汇编代码已转化为 “.o” 的二进制目标代码。

  • 实例:

    gcc -c test.s -o test.o
    

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

  • 生成的编译之后,就进入了连接阶段。

  • 实例:

    gcc test.s -o test
    

1-2 动态链接和静态链接

1-2-1 静态链接

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

  1. 浪费空间

因为每个可执行程序中对所有需要的目标文件都要有一份副本。
静态链接中,所有依赖的库函数(例如 printf())的机器码会直接被复制进每一个可执行文件。
所以如果多个程序都调用了同一个库函数(例如 C 标准库中的 printf()),那么这些程序都会在自己的二进制文件中保存一份 printf.o 的副本。
结果是:同一个函数的代码会在内存或磁盘中存在多份冗余拷贝,浪费空间。

  1. 更新困难

每当库函数的代码修改时,所有使用该库的程序都必须重新编译、重新链接。
如果库函数有 bug 或逻辑更新,静态链接的程序并不会自动使用新版库。
必须手动重新编译并重新生成可执行文件,才能包含更新后的函数代码。
优点是:静态链接的程序在运行时不依赖外部库文件,执行速度快、部署方便。

动态链接的出现解决了静态链接中提到问题。

1-2-2 动态链接

动态链接把程序拆成多个独立模块,在程序运行时再把这些模块加载并链接在一起,形成完整的可执行程序。
不像静态链接那样在编译期就把所有目标文件都打包成一个大的可执行文件。
例如:

test 程序在运行时会依赖某个 C 语言的动态链接库(比如 libc.so)。
在这里插入图片描述
当程序执行时,操作系统的动态链接器会在内存中加载这个共享库。
注 :ldd命令用于打印程序或者库文件所依赖的共享库列表。

1-2-3 库的概念

  • 我们的C程序中,并没有定义 “printf” 的函数实现,且在预编译中包含的 “stdio.h” 中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现 “printf” 函数的呢?
  • 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径 “/usr/lib” 下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数 “printf” 了,而这也就是链接的作用。

1-3 静态库和动态库

1-3-1 静态库和动态库的介绍

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为 “.a”

  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 “.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。

    gcc test.o -o test
    
  • gcc 默认生成的二进制程序,是动态链接的。(file命令进行查看)

在这里插入图片描述

注意:

Linux下,动态库XXX.so,静态库XXX.a
Windows下,动态库XXX.dll,静态库XXX.lib

对于libc.so的逐词解析

部分含义说明
lib“library” 的缩写表示这是一个库文件(Library)。几乎所有 Linux 下的库文件名都以 lib 开头。例如:libm.so(数学库),libpthread.so(线程库)。
c“C language” 的缩写表示这是 C 语言标准库(C library),也叫 libc。它提供了 C 语言中最基础的函数实现。
.so“shared object” 的缩写表示这是一个 共享对象文件(Shared Object),即动态链接库。它在程序运行时被加载,而不是在编译时直接打包进可执行文件。

有了上面的论述,我们来简单看一下两者的区别。

1-3-2 静态库与动态库的区别

  • 动态链接生成的可执行文件体积较小,因为库代码不会被打包进程序,而是由多个程序共享。
  • 静态链接的可执行文件不再依赖库文件;而动态链接程序在运行时必须依赖外部动态库(如 .so 文件)存在,否则无法运行。
  • 静态链接程序在内存中会出现重复的库代码副本,而动态链接程序共享同一份库映像,节省内存。
  • 动态链接通过共享库文件,减少重复代码加载,因此节省内存与磁盘空间,但会稍微增加启动时的加载开销(加载动态库)。

1-3-3 理论验证

前面我们只是再说理论知识,接下来我们来对一个程序分别来进行静态链接和动态链接,进行对比。
前面说过并测试,gcc默认是动态链接,所以我们先来进行动态链接:

在这里插入图片描述

下面来进行静态链接,如果我们是云服务器,一般C/C++的静态库并没有安装,先安装:

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

静态链接的指令只需要多加个-static即可:

在这里插入图片描述
在这里插入图片描述
可见,大小差别还是很大的。

对于动静态库我们就简单了解到这,后续还会有更深入的文章来讲解。

1-4 gcc其它常用选项

选项说明
-E只进行预处理(Preprocessing),不编译、不汇编、不链接。结果输出到标准输出(通常要用 > 重定向保存到文件)。
-S只编译到汇编代码(Assembly),不进行汇编和链接。生成 .s 文件。
-c只编译并汇编生成目标文件(Object File),不进行链接。生成 .o 文件。
-o <文件名>指定输出文件名(可用于目标文件或可执行文件)。
-static对生成的可执行文件采用完全静态链接(不依赖动态库)。要求系统安装对应的静态库(如 glibc-static)。
-g在生成的目标文件或可执行文件中加入调试信息,供 GDB(GNU 调试器)使用。
-shared告诉编译器生成一个共享库(Shared Library),即 .so 文件;通常与 -fPIC 一起使用。
-O0 / -O1 / -O2 / -O3控制编译优化等级
- -O0:无优化(默认),编译最快、调试方便;
- -O1:基本优化;
- -O2:更激进的优化,几乎不影响调试;
- -O3:最高级别优化,可能增加编译时间或代码体积。
-w禁止所有警告信息输出。
-Wall打开大多数常见的警告信息(建议总是加上)。

二、调试器gdb/cgdb

2-1 预备知识与准备工作

  • 我们知道,程序的发布方式有两种, debug 模式和 release 模式, Linux gcc/g++ 出来的二进制程序,默认是 release 模式。
  • 要使用gdb/cgdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项,如果没有添加,程序无法被编译。

安装调试器
对于调试器的学习,我的建议是从 cgdb 入门 + 同步掌握 gdb 命令。因为gdb对于新手来说确实比较复杂一点,cgdb有类似 vim 的界面。当然,如果想直接使用gdb,要是没有问题的,我下面演示命令也是直接用gdb的。
安装cgdb,可以切换root身份或使用sudo指令进行安装:

yum install -y cgdb

示例代码
要进行调试,我们先准备一个简单的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;
}

注意
上面的代码编译时可能会出现下面的问题:
在这里插入图片描述

这是因为此gcc版本并未支持c99标准,所以我们这样编译即可:

gcc test.c -o test -std=c99 -g

准备工作做完,下面,我们就来进行实操。

2-2 常见调试命令

开始gdb binFile
退出ctrl + d 或命令quit / q
调试命令速览表

命令作用示例
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 行号执行到指定行号(没断点时)until 20
backtrace/bt查看当前执行栈的各级函数调用及参数backtrace
info/i locals查看当前栈帧的局部变量值info locals
quit退出 GDB 调试器quit

命令演示:

  • list/l

    list/l + #表示以#为中心显示10行。
    在这里插入图片描述
    在这里插入图片描述
    注意:不管你在l/list后加某一个合理的参数,都是以参数为中心展示10行的。
    比如:list/l 函数名 或者list/l 文件名:行号

  • r/run

    从程序开始连续执行。
    如果没有断点,直接运行结束。
    实例:
    在这里插入图片描述

  • break/b 函数名break/b [文件名:]行号

    在函数开头或者指定位置设置断点 。
    实例:
    在这里插入图片描述

  • info break/b

    查看当前所有断点的信息
    实例:
    在这里插入图片描述
    在这里插入图片描述

  • n/nexts/step

    n/next 单步执行,不进入函数内部,相当于vs中的F10,s/step 单步执行,进入函数内部,相当于vs中的F11.
    自行测试吧。

  • delete/d breakpointsdelete/d breakpoints n

    在这里插入图片描述

  • disable breakpoints ndisable breakpointsenable breakpoints

    禁用与恢复断点
    在这里插入图片描述

对于常用的命令就上面这些,多练习即可。
在我们学习gdb时,我们可以与vs环境下的调试进行对比理解,例如:

  • r 相当于vs中的 F5;
  • b 相当于vs中的 设置断点;
  • n 相当于vs中的 F10;
  • s 相当于vs中的 F11(在函数处);
  • p/display 相当于vs中的 监视;

2-3 常见调试技巧

接下来我们学习三个实用的调试技巧。
watch
执行时监视一个表达式(如变量)的值。如果监视的表达式在程序运行期间的值发生变化,GDB会暂停程序的执行,并通知使用者。
比如:如果你有一些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变化了,就会通知你。
set var确定问题原因
例如,假设你在调试时发现某个变量值不正确,可能导致程序崩溃或结果错误。你可以使用 set var 来更改该变量的值,进而观察程序的行为变化,从而确定问题的原因。
基本用法

set var <变量名> = <新值>

实例
在这里插入图片描述

条件断点
添加条件断点

b 9 if i == 30 # 9是行号,表示新增断点的位置

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

condition 2 i== 30 #给2号断点,新增条件 i == 30

总结

本文系统性地介绍了Linux环境下C/C++开发中两个核心工具:编译器gcc/g++和调试器gdb/cgdb。通过深入理解编译过程的四个阶段(预处理、编译、汇编、链接)以及动静态链接机制,我们能够更好地掌控程序的构建过程。同时,掌握gdb/cgdb的调试技巧,能够显著提升排查和修复代码问题的效率。


如果本文对您有启发:
点赞 - 让更多人看到这篇硬核技术解析 !
收藏 - 实战代码随时复现
关注 - 获取Linux系列深度更新
您的每一个[三连]都是我们持续创作的动力!

请添加图片描述

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

相关文章:

  • LeetCode算法日记 - Day 96: 最长回文子串
  • 汽车ECU诊断刷写和OTA升级中的验签和校验
  • 网站主题旁边的图标怎么做的套模板网站
  • x265 编码器Analysis::compressInterCU_rd0_4 函数详细分析
  • 小杰-大模型(two)——RAG与Agent设计——Langchain-prompt提示词
  • Rust 练习册 :Luhn Trait与Trait实现
  • 家庭机器人,从科幻到日常的二十年突围战
  • 网站html地图导航代码大全网站功能的介绍
  • Android开发(Kotlin) 高阶函数、内联函数
  • AI安全与网络安全的融合:从挑战到解决方案
  • 从零开始构建现代化React应用:最佳实践与性能优化
  • 国外的网站建设公司广州工商注册服务中心
  • 【tips】常用不同状态小圆点样式css
  • 保险微网站制作公司网站费用计入什么科目
  • SSM网上水果商城s7436(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 北京市建设信息网站湖南手机版建站系统信息
  • 【函数参数传递方式选择指南(C/C++)】
  • 做ppt的图片素材网站数字营销成功案例
  • 企业网站子页面模板网站 开发 外包
  • 机器学习日报14
  • 解决Mac不能识别#include <bits/stdc++.h> 头文件问题
  • 基于站点数据进行遥感机器学习参数反演-以XGBOOST反演LST为例(附带数据与代码)试读
  • 四面山网站建设现在帮别人做网站赚钱不
  • 破解EEG逆问题:ADMM-ESINet如何融合优化理论与深度学习实现实时源成像
  • CSS 高中低部分面试题方法及知识点介绍
  • GMI Cloud@AI周报 | Cursor 2.0发布自研模型Composer;小鹏发布新一代人形机器人 IRON
  • 莱芜手机网站建设报价网站建设平台策划
  • 【jmeter】-安装-插件安装
  • 猫头虎AI分享:CodeBuddy IDE 已支持 GLM-4.6!亲测更强了
  • 云手机能够流畅运行大型游戏吗