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
预处理文件的最后结果如下:
我们可以看到文件比原来大了好多,预处理将头文件iostream展开,其他部分基本不变。
2.编译-Compiling
编译生成.s文件,编译过程是将C代码转换成汇编语言。
# -S 编译选项告诉 g++ 为 C++ 代码产生汇编语言后停止编译
# g++ 产生的汇编语言文件的缺省扩展名是 .s
g++ -S test.i -o test.s
产生的.s文件的部分结果如下:
可以看多很多汇编指令。
3.汇编-Assembling
汇编是将编译生成的.s文件转换为二进制语言,生成一个.o文件。
# -c 编译选项告诉 g++ 为 C++ 代码编译为机器语言停止编译
# 缺省时 g++ 建立的目标代码有一个 .o的扩展名
g++ -c test.s -o test.o
test.o文件部分结果如如下:
可以看到是乱码,这是因为vim不识别机器语言导致的。
4.链接-Linking
链接需要的其他文件后生成可执行文件。
# -o 编译选项来为将产生的可执行文件用指定的文件名
g++ test.o -o test
运行可执行文件,使用./test来运行,结果如下:
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
- -Wall 打印警告信息
- -w 关闭警告信息
- -std=c++11 设置编译标准
- -o指定输出文件名 缺省会输出一个a.out的文件
- -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++编译器对目录结构如下的多文件程序进行编译。
三个文件的内容如下:
// 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的可执行文件,执行结果如下:
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运行该文件,运行结果如下:
- 链接动态库生成可执行文件:
## 进入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
运行结果如下:
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:
1.编译程序时加上-g,之后才能用gdb -g main.c -o main
2.回车键:重复上一命令