Linux系统 利用g++进行C++的多文件调试运行

Linux系统下大家的编译器环境应该都是安装的gcc编译器,调试器是gdb,我们可以通过gcc编译器对C++文件的编译过程,对源代码的编译过程有一个更好的了解,这有助于我们自己编写一些vscode的js文件。

1.利用gcc编译器进行C++多文件编译和运行


首先我们要了解gcc的编译过程。

1.1 gcc编译过程


gcc编译过程分为4步:预处理、编译、汇编、链接
下面以一个简单的输出文件test.cpp来演示gcc的这四步编译过程。
test.cpp代码如下:

#include <iostream>
using namespace std;
int main()
{
        cout << "This my first cpp programer in Linux" << endl;
        return 0;
}

1.预处理-Pre-Procession
预处理生成的是.i文件,实际上就是将include的头文件展开。

# -E 选项指示编译器对输入文件进行预处理
g++ -E test.cpp -o test.i

预处理文件的最后结果如下:
image
我们可以看到文件比原来大了好多,预处理将头文件iostream展开,其他部分基本不变。

2.编译-Compiling
编译生成.s文件,编译过程是将C代码转换成汇编语言。

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

产生的.s文件的部分结果如下:
image
可以看多很多汇编指令。

3.汇编-Assembling
汇编是将编译生成的.s文件转换为二进制语言,生成一个.o文件。

# -c   编译选项告诉 g++ 为  C++  代码编译为机器语言停止编译
# 缺省时 g++ 建立的目标代码有一个  .o的扩展名
g++ -c test.s -o test.o

test.o文件部分结果如如下:
image
可以看到是乱码,这是因为vim不识别机器语言导致的。

4.链接-Linking
链接需要的其他文件后生成可执行文件。

# -o 编译选项来为将产生的可执行文件用指定的文件名
g++ test.o -o test

运行可执行文件,使用./test来运行,结果如下:
image

1.2 g++重要编译参数


1.-g 编译带调试信息的可执行文件

# -g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息,以调试程序。

# 产生带调试信息的可执行文件test
g++ -g test.cpp -o test

2.-O[n] 优化源代码

## 所谓优化,例如省略掉代码中从未使用的变量、直接将常量表达式用结果值代替等等,这些操作会所见所包含的代码量,提高最终生成的可执行文件的运行效率

# -O 选项告诉 g++ 对源代码进行基本的优化。这些优化在大多数情况下都会是程序执行的更快。 -O 选项告诉g++产生尽可能小和尽可能快的代码。

# -O 同时减小代码的长度和执行时间,效果等价与-O1
# -O0 表示不做优化
# -O1 为默认优化
# -O2 完成-O1的优化之后,进行一些额外的调整工作,如指令调整等
# -O3 包括循环展开和其他一些与处理特性相关的优化工作
# 选项将使编译的速度比 -O 时慢,但通常产生的代码执行速度更快。

# 使用 -O2优化源代码,并输出可执行文件
g++ -O2 test.cpp -o test_with_O2

3.-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 -l libmytest test.cpp

4.-I 指定头文件搜索目录

# -I
# /usr/include目录一般不用指定,gcc知道去那里找,但是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,不加会得到一个“xxxx.h:No such file or directory”的错误,-I也可以用相对路径来指定。

g++ -I/myinclude test.cpp
  1. -Wall 打印警告信息
  2. -w 关闭警告信息
  3. -std=c++11 设置编译标准
  4. -o指定输出文件名 缺省会输出一个a.out的文件
  5. -D 定义宏
# 在使用gcc/g++编译的时候定义宏

# 常用场景
# -DDEBUG 定义DEBUG宏,可能文件中有DEBUG宏的相关信息,用个DDEBUG来选择开启或关闭DEBUG

样例如下:

// -Dname 定义宏name,默认定义内容为字符串“1”

# include <stdio.h>
{
	using std::cout;
	#ifdef DEBUG
		printf("DEBUG LOG\n");
	#endif
		printf("in\n");
}

1.3 使用g++命令行编译

使用g++编译器对目录结构如下的多文件程序进行编译。
image
三个文件的内容如下:

// include file and definition of prototype
#include <iostream>
void Swap(int &a, int &b);
// definition of function
#include "Swap.h"
void Swap(int &a, int &b)
{
        int temp;
        temp = a;
        a = b;
        b = temp;
}
// main function
#include "Swap.h"

int main()
{
        using namespace std;
        int a = 10;
        int b = 20;
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        Swap(a,b);
        cout << "After Swap(a,b)" << endl;
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
        return 0;
}
--- ####1.3.1 直接编译 --- 在终端输入如下指令, ``` g++ main.cpp ./src/Swap.cpp -Iinclude ``` 会产生一个 a.out的可执行文件,执行结果如下: ![image](https://img2022.cnblogs.com/blog/2636982/202201/2636982-20220123160556048-1279486076.png) --- ####1.3.2 生成库文件并编译 --- - 链接**静态库**生成可执行文件: ``` ## 进入src目录下 cd src

汇编,生成Swap.o文件 -c和-I的顺序可以颠倒

g++ Swap.cpp -c -I../include

生成静态库libSwap.a

ar rs libSwap.a Swap.o

回到上级目录

cd ..

链接,生成可执行文件:staticmain

g++ main.cpp -Iinclude -lSwap -Lsrc -o staticmain

通过./staticmain运行该文件,运行结果如下:
![image](https://img2022.cnblogs.com/blog/2636982/202201/2636982-20220123161728014-1511558784.png)

- 链接动态库生成可执行文件:

进入src目录下

cd src

生成动态库libSwap.so

g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so

上面的命令等价于以下两条命令

g++ Swap.cpp -I../include -c -fPIC

gcc -shared -o libSwap.so Swap.o

回到上级目录

cd ..

链接,生成可执行文件:dyna_main

g++ main.cpp -Iinclude -lSwap -Lsrc -o dyna_main

输入./dyna_main,来运行该文件,会提示运行错误:
./dyna_main: error while loading shared libraries: libSwap.so: cannot open shared object file: No such file or directory
出现该错误的原因是需要添加libSwap.so的路径,通过以下命令运行:
LD_LIBRARY_PATH=src ./dyna_main
运行结果如下:
![image](https://img2022.cnblogs.com/blog/2636982/202201/2636982-20220123164957382-1595077492.png)

###2. 使用gdb调试器对代码进行调试
GDB介绍:
- GDB(GNU Debugger)是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发C/C++最常用的调试器。
- 程序员可以使用**GDB来跟踪程序中的错误**,从而减少程序员的工作量。
- Vscode是通过GDB调试其来实现C/C++调试工作的。

GDB主要功能:
- 设置断点(断点可以是条件表达式)
- 使程序在指定代码上暂停执行,便于观察
- 单步执行程序,便于调试
- 查看程序中变量值的变化
- 动态改变程序的执行环境
- 分析崩溃程序产生的core文件

####4.1 常用调试命令参数
调试执行: 执行gdb[exefilename],进入gdb调试程序,其中exefilename为要调试的可执行文件名

以下命令后括号内为命令的简化使用,比如rur(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 # 设置变量的值

(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行设置断点

(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())

**Tips:<br>1.编译程序时加上-g,之后才能用gdb -g main.c -o main<br>2.回车键:重复上一命令**

</font>
posted @ 2022-01-23 18:53  Fight!GO  阅读(1190)  评论(0编辑  收藏  举报