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

【Linux下的cpp】编译调试(gcc、g++、gdb)

【Linux下的cpp】编译调试(gcc、g++、gdb)

文章目录

  • 【Linux下的cpp】编译调试(gcc、g++、gdb)
    • 简述gcc、g++、gdb
    • 编译过程
    • g++ 编译参数
    • 命令行编译演练
      • 1、直接编译
      • 2、生成库文件并编译
        • 链接静态库并生成可执行文件
        • 链接动态库生成可执行文件
    • gdb调试器
      • 1. 常用调试命令参数
        • 常用命令演示
      • 2. gdb 切换线程
      • 3. gdb命令行调试演练

不管是从事何种语言开发,合适的IDE以及开发环境是必不可少的。

简述gcc、g++、gdb

相同点

  • GNU 项目:它们都是 GNU 项目的一部分,并且都是开源的。
  • 命令行工具:它们都是命令行工具,适用于 Unix/Linux 环境。
  • 开发工具链:它们都是开发工具链的一部分,用于编译和调试程序。

不同点

  • 功能:

    • gcc:主要用于编译 C 语言程序。
    gcc -o myprogram myprogram.c
    
    • g++:主要用于编译 C++ 语言程序。
    g++ -o myprogram myprogram.cpp
    
    • gdb:用于调试程序,而不是编译。
    gdb myprogram
    
  • 输入和输出:

    • gccg++:接受源代码文件(如 .c.cpp)并生成目标文件或可执行文件。
    • gdb:接受可执行文件,用于调试运行中的程序。

总结

  • gcc 是一个通用编译器,主要用于编译 C 语言代码。
  • g++ 是一个专门的编译器,主要用于编译 C++ 语言代码。
  • gdb 是一个调试器,用于调试 C、C++ 及其他语言编写的程序。

编译过程

编译单个c++文件:g++ test.cpp -o test

单次编译命令

预处理 -》 编译 -》 汇编 -》链接

# 预处理
# -E 指示编译器仅对输入文件进行预处理
# -o  指定输出文件(这里是test.i)
g++ -E test.cpp -o test.i		// 生成.i文件

# 编译
# -S 让g++在为C++代码生成汇编语言文件后停止编译
# g++ 产生的汇编语言文件的缺省扩展名是 .s
g++ -S test.i -o test.s

# 汇编
# -c 让g++把源代码编译为机器语言的目标代码(只编译,不链接)
g++ -c test.s -o test.o

# 链接
# 生成可执行文件
g++ test.o -o test

g++ 编译参数

  1. -g 编译带调试信息的可执行文件
# -g 选项告诉 GCC 产生能被 GNU 调试器GDB使用的调试信息,以调试程序。
# 产生带调试信息的可执行文件test
g++ -g test.cpp -o test
  1. -O[n] 优化源代码
## 所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。

# -O 选项告诉 g++ 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 -O2 选项告诉 g++ 产生尽可能小和尽可能快的代码。 如-O2,-O3,-On(n 常为0–3)
# -O 同时减小代码的长度和执行时间,其效果等价于-O1
# -O0 表示不做优化
# -O1 为默认优化
# -O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。
# -O3 则包括循环展开和其他一些与处理特性相关的优化工作。
# 选项将使编译的速度比使用 -O 时慢, 但通常产生的代码执行速度会更快。

# 使用 -O2优化源代码,并输出可执行文件
g++ -O2 test.cpp
  1. -l | -L 指定库文件/ 指定库文件路径
# -l参数(小写)就是用来指定程序要链接的库,-l参数紧接着就是库名
# 在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接

# 链接glog库
g++ -lglog test.cpp

# 如果库文件没放在上面三个目录里,需要使用-L参数(大写)指定库文件所在目录
# -L参数跟着的是库文件所在的目录名

# 链接mytest库,libmytest.so在/home/bing/mytestlibfolder目录下
g++ -L/home/bing/mytestlibfolder -lmytest test.cpp
  1. -I 指定头文件搜索目录
# -I
# /usr/include目录一般是不用指定的,gcc知道去那里找,
#但是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,
# 比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude 参数了,如果不加你会得到一个”xxxx.h: No such file or directory”的错误。
# -I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。上面我们提到的–cflags参数就是用来生成-I参数的。

g++ -I/myinclude test.cpp
  1. -W 打印警告信息
  2. -w 关闭警告信息
  3. -std=c++11 设置编译标准
  4. -o 指定输出文件名
  5. -c 只编译,不链接
  6. -D 定义宏

image-20240618182132673

命令行编译演练

1、直接编译

# 将 main.cpp src/Swap.cpp 编译为可执行文件
g++ main.cpp src/swap.cpp -Iinclude
# 运行a.out
./a.out

image-20240618210958060

2、生成库文件并编译

注意源文件list 可以放在g++之后,也可以放在命令行最后

链接静态库并生成可执行文件
  1. 简单生成静态库(-c则是编译选项)
    • 语法:g++ -c -o libpublic.a public.cpp
    • 包含头文件则添加-I相对路径
  2. 指定库:
    • -l+指定库名(即libxxxx.a中的xxxx)
    • -L指定库目录(即静态库文件放在哪里)
  3. 链接不带任何限制参数(-o的作用是指定名称,而非链接选项)
# 将需要用到的库编译成机器语言文件
# g++ 源文件 -c(转为机器语言) -I(头文件目录)
# 将生成swap.o
g++ swap.cpp -c -I../include

# 生成静态库swap.a(这里的lib是前缀,只要生成静态库都需要这个前缀)
ar rs libswap.a swap.o

# 将main.cpp编译为可执行的static_main
# g++ 源文件 -l指定库文件 -L指定库目录 -I指定头文件目录 -o 可执行文件名
g++ main.cpp -lswap -Lsrc -Iinclude -o static_main

image-20240618214558253

链接动态库生成可执行文件
  1. 制作动态库g++ -fPIC -shared -o lib库名.so 源代码文件清单
    • -fPIC -shared:表示制作动态库
  2. 使用动态库需要改变环境变量选项:
    1. echo $LD_LIBRARY_PATH:查看当前库环境变量
    2. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库所在目录的绝对路径
# 生成动态库libswap.so
g++ swap.cpp -I../include -fPIC -shared -o libswap.so

# 生成可执行文件
# g++ 源文件 -I头文件目录 -l库名 -L库目录 -o 指定程序名
g++ main.cpp -Iinclude -lswap -Lsrc -o dynamic_main

# 运行方法一,手动指定动态库目录和运行文件
# 执行动态库生成后的可执行文件
# =指定库目录  可执行文件
LD_LIBRARY_PATH=src ./dynamic_main

# 运行方法二:将动态库所在目录加入系统的动态库环境变量中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库所在目录的绝对路径

gdb调试器

gdb调试器是Linux下系统开发C/C++最常用的调试器。

Tips:

  1. 编译程序时需要加上-g,之后才能用gdb进行调试:g++ -g main.cpp -o main

  2. 回车键:重复上一命令

1. 常用调试命令参数

调试开始:执行gdb [exefilename] ,进入gdb调试程序,其中exefilename为要调试的可执行文件名

## 以下命令后括号内为命令的简化使用,比如run(r),直接输入命令 r 就代表命令run

$(gdb)help(h)	# 查看命令帮助,具体命令查询在gdb中输入help + 命令

$(gdb)run(r)	# 重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件)

$(gdb)start		# 单步执行,运行程序,停在第一行执行语句

$(gdb)list(l) 	# 查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数)

$(gdb)set		# 设置变量的值,如:1. set args a b c (当main函数带参时); 2. 改变内部变量的值 set var name = "aaa"

$(gdb)next(n)   # 单步调试(逐过程,函数直接执行)

$(gdb)step(s)	# 单步调试(逐语句:跳入自定义函数内部执行)

$(gdb)backtrace(bt)	# 查看函数的调用的栈帧和层级关系

$(gdb)frame(f) 	# 切换函数的栈帧

$(gdb)info(i) 	# 查看函数内部局部变量的数值

$(gdb)finish	# 结束当前函数,返回到函数调用点

$(gdb)continue(c)	# 继续运行

$(gdb)print(p)	# 打印值及地址

$(gdb)quit(q) 	# 退出gdb

$(gdb)break+num(b)			# 在第num行设置断点, b20行设置断点

$(gdb)info breakpoints		# 查看当前设置的所有断点

$(gdb)delete breakpoints num(d)	# 删除第num个断点

$(gdb)display				# 追踪查看具体变量值

$(gdb)undisplay				# 取消追踪观察变量

$(gdb)watch					# 被设置观察点的变量发生修改时,打印显示

$(gdb)i watch				# 显示观察点

$(gdb)enable breakpoints	# 启用断点

$(gdb)disable breakpoints	# 禁用断点

$(gdb)x						# 查看内存x/20xw 显示20个单元,16进制,4字节每单元

$(gdb)run argv[1] argv[2]	# 调试时命令行传参

$(gdb)set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork())
常用命令演示
# 启动调试可执行文件
rdjroot@TM:~/Coding/cppnet$ gdb server

# 设置main函数的入参,第一个可执行文件已省略
(gdb) set args 5005

# 设置跟踪文件
(gdb) file ~/Coding/cppnet/server
# 展示90行左右的代码
(gdb) list 90

# 设置断点在88行
(gdb) b 88

# start开始执行,会在main函数前停止,直到continue.
# run会直接执行
(gdb) start

# 设置监视变量(公式)
(gdb) display eventfd

# 删除监视变量
(gdb) undisplay 1(这里是变量的id或者变量名)

# 显示所有的监视变量
(gdb) info display

# 退出调试
(gdb) quit

2. gdb 切换线程

常用的 thread 命令示例:

  1. info threads:显示当前程序中所有线程的信息,包括线程号和调用堆栈。

  2. thread <thread-id>:切换到指定线程。您可以使用线程号或者线程索引(从0开始)来切换到相应的线程。

  3. info inferiors:显示所有程序执行实例的信息,每个实例对应一个线程组。

  4. thread apply <thread-id-list> <gdb-command>:对指定线程列表执行命令。例如,thread apply 1 2 3 bt将对线程1、2和3依次执行 bt 命令(打印调用堆栈)。

通过这些命令,您可以方便地管理和调试多线程程序。

3. gdb命令行调试演练

使用gdb命令打开用-g生成的可执行文件,进行调试

image-20240619215604801

4、gdb调试core文件

  1. 修改core限制
# 查看系统限制参数
ulimit -a 
# 修改core问价大小为無限
ulimit -c unlimited
  1. 再次启用运行程序,如果出错会产生core.xxx文件
  2. 调试core文件:gdb core.xxx runexe。->进入gdb命令界面,会显示错误行数
  3. bt查看函数调用栈

5、调试正在运行的程序

  1. 使用命令查看正在执行程序的进程编号:ps -ef | grep run_exe

image-20240701193421469

  1. gdb run_exe -p process_num - > 会使正在运行的程序暂停

修改core限制

# 查看系统限制参数
ulimit -a 
# 修改core问价大小为無限
ulimit -c unlimited
  1. 再次启用运行程序,如果出错会产生core.xxx文件
  2. 调试core文件:gdb core.xxx runexe。->进入gdb命令界面,会显示错误行数
  3. bt查看函数调用栈

5、调试正在运行的程序

  1. 使用命令查看正在执行程序的进程编号:ps -ef | grep run_exe

[外链图片转存中…(img-5Md3n8KT-1726372189697)]

  1. gdb run_exe -p process_num - > 会使正在运行的程序暂停

http://www.kler.cn/a/307262.html

相关文章:

  • vue elementui el-dropdown-item设置@click无效的解决方案
  • VSCode可以安装最新版,并且可以对应Node 12和npm 6
  • 小程序中引入下载到本地的iconfont字体图标加载不出来问题解决
  • 统信UOS开发环境支持Electron
  • (十)Python字典基本操作
  • 产品经理如何使用项目管理软件推进复杂项目按时上线
  • 工程师 - ACPI和ACPICA的区别
  • [Redis] Redis中的Hash类型和List类型
  • 29 线性表 · 队列
  • 【人工智能】Transformers之Pipeline(十八):文本生成(text-generation)
  • C语言实现贪吃蛇小游戏
  • 【技术科普】揭秘图像处理:从零开始的计算机视觉之旅!
  • 海量数据查找最大K个值:数据结构与算法的选择
  • 【Node.js】初识微服务
  • CANopen协议的理解
  • 不用禁用 iptables 来解决 UFW 和 Docker 的安全问题
  • 智汇创想pytest接口自动化测试框架
  • 通俗地类比计算机视觉中各种层或操作的作用
  • 自动曝光算法
  • IDEA 常用插件推荐,美观又实用!
  • Vue生命周期;Vue路由配置;vue网络请求;vue跨域处理
  • vue3+ts 使用amCharts展示地图,1.点击左侧国家,可以高亮并放大右侧地图对应的国家。 2.展示数据球。
  • python tkinter
  • 物联网智能项目
  • Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化
  • 腾讯云使用