【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
-
输入和输出:
gcc
和g++
:接受源代码文件(如.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++ 编译参数
-g
编译带调试信息的可执行文件
# -g 选项告诉 GCC 产生能被 GNU 调试器GDB使用的调试信息,以调试程序。
# 产生带调试信息的可执行文件test
g++ -g test.cpp -o test
-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
-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
-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
-W
打印警告信息-w
关闭警告信息-std=c++11
设置编译标准-o
指定输出文件名-c
只编译,不链接-D
定义宏
命令行编译演练
1、直接编译
# 将 main.cpp src/Swap.cpp 编译为可执行文件
g++ main.cpp src/swap.cpp -Iinclude
# 运行a.out
./a.out
2、生成库文件并编译
注意源文件list 可以放在
g++
之后,也可以放在命令行最后
链接静态库并生成可执行文件
- 简单生成静态库(
-c
则是编译选项)- 语法:
g++ -c -o libpublic.a public.cpp
- 包含头文件则添加
-I相对路径
- 语法:
- 指定库:
-l
+指定库名(即libxxxx.a
中的xxxx
)-L
指定库目录(即静态库文件放在哪里)
- 链接不带任何限制参数(
-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
链接动态库生成可执行文件
- 制作动态库
g++ -fPIC -shared -o lib库名.so 源代码文件清单
-fPIC -shared
:表示制作动态库
- 使用动态库需要改变环境变量选项:
echo $LD_LIBRARY_PATH
:查看当前库环境变量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:
编译程序时需要加上-g,之后才能用gdb进行调试:g++ -g main.cpp -o main
回车键:重复上一命令
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
命令示例:
-
info threads
:显示当前程序中所有线程的信息,包括线程号和调用堆栈。 -
thread <thread-id>
:切换到指定线程。您可以使用线程号或者线程索引(从0开始)来切换到相应的线程。 -
info inferiors
:显示所有程序执行实例的信息,每个实例对应一个线程组。 -
thread apply <thread-id-list> <gdb-command>
:对指定线程列表执行命令。例如,thread apply 1 2 3 bt
将对线程1、2和3依次执行bt
命令(打印调用堆栈)。
通过这些命令,您可以方便地管理和调试多线程程序。
3. gdb命令行调试演练
使用gdb命令打开用-g
生成的可执行文件,进行调试
4、gdb调试core文件
- 修改
core
限制
# 查看系统限制参数
ulimit -a
# 修改core问价大小为無限
ulimit -c unlimited
- 再次启用运行程序,如果出错会产生core.xxx文件
- 调试core文件:
gdb core.xxx runexe
。->进入gdb命令界面,会显示错误行数 bt
查看函数调用栈
5、调试正在运行的程序
- 使用命令查看正在执行程序的进程编号:
ps -ef | grep run_exe
gdb run_exe -p process_num
- > 会使正在运行的程序暂停
修改core
限制
# 查看系统限制参数
ulimit -a
# 修改core问价大小为無限
ulimit -c unlimited
- 再次启用运行程序,如果出错会产生core.xxx文件
- 调试core文件:
gdb core.xxx runexe
。->进入gdb命令界面,会显示错误行数 bt
查看函数调用栈
5、调试正在运行的程序
- 使用命令查看正在执行程序的进程编号:
ps -ef | grep run_exe
[外链图片转存中…(img-5Md3n8KT-1726372189697)]
-
gdb run_exe -p process_num
- > 会使正在运行的程序暂停
参考:
[1] 基于VSCode和CMake实现的C/C++开发-Linux篇