Linux - gcc/g++和gdb
1. gcc编译器
GCC 是 Linux 下的编译工具集,是 GNU Compiler Collection 的缩写,包含 gcc、g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar、nm 等。
GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C、Pascal、Fortran、Java、Ada、Go 等语言均能进行编译。GCC 在可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等软件平台。
1.1 安装 GCC
有些纯净版的 Linux 默认没有 gcc 编译器,需要自己安装,在线安装步骤如下:
# 安装软件必须要有管理员权限
# ubuntu
$ sudo apt update # 更新本地的软件下载列表, 得到最新的下载地址
$ sudo apt install gcc g++ # 通过下载列表中提供的地址下载安装包, 并安装
# centos
$ sudo yum update # 更新本地的软件下载列表, 得到最新的下载地址
$ sudo yum install gcc g++ # 通过下载列表中提供的地址下载安装包, 并安装
gcc 安装完毕之后,可以查看版本:
# 查看 gcc 版本
$ gcc -v
$ gcc --version
# 查看 g++ 版本
$ g++ -v
$ g++ --version
1.2 gcc 工作流程
GCC 编译器对程序的编译分为 4 个阶段:预处理(预编译)、编译和优化、汇编和链接。gcc编译.c文件(c语言程序),g++编译.cpp文件(c++程序)。GCC 的编译器可以将这 4 个步骤合并成一个。 先介绍一个每个步骤都分别做了些什么事儿
- 预处理:在这个阶段主要做了三件事: 展开头文件 、宏替换 、去掉注释行( 这个阶段需要 GCC 调用预处理器来完成,最终得到的还是源文件,文本格式)
- 编译:这个阶段需要 GCC 调用编译器对文件进行编译,最终得到一个汇编文件
- 汇编:这个阶段需要 GCC 调用汇编器对文件进行汇编,最终得到一个二进制文件
- 链接:这个阶段需要 GCC 调用链接器对程序需要调用的库进行链接,最终得到一个可执行的二进制文件
准备: 先在当前目录下使用vim新建一个.c文件 例如 test.c
$ vim test.c
插入一段代码。
#include <stdio.h>
#define NUM 100
int main()
{
printf("hello world!\n");
printf("hello world!\n");
//printf("hello world!\n");
//printf("hello world!\n");
printf("NUM = %d",NUM);
return 0;
}
1.2.1 预处理
1.预处理通过对宏定义(像#define)进行展开,对头文件(像 stdio.h)进行展开,对条件进行(像ifdef)编译,展开所有宏,删除所有注释(像"//").预处理cpp把源代码,头文件预编成一个.i文件。(注意这时并不检查语法,所以即使有语法错误也不会报错。)
2.命令:
$ gcc -E (源文件名) -o (预处理文件名)
或者:
$ gcc (源文件名) > (预处理文件名)
例如按tset.c 来说,预处理时可以是 :
gcc -E test.c -o test.i
也可以是
gcc test.c > test.i
查看test.i与test.c
1.2.2 编译
1.编译也就是检查语法是否错误,将预处理过的文件编译成汇编文件。
2.命令:
$ gcc -S (源文件) -o (汇编文件)
例如
gcc -S test.i -o test.s
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查 无误后,gcc 把代码翻译成汇编语言。
1.2.3 汇编
1. 汇编也就是将汇编文件生成目标文件(二进制文件)通过汇编,文本代码变成了二进制代码。(二进制代码文件以.o为后缀名)。
2.命令:
$ gcc -c (汇编文件) -o (目标文件)
例如
gcc -c test.s -o test.o
汇编阶段的工作是将编译产生的汇编文件转化为.o二进制目标文件。.o文件就是VS编译器下的.obj文件。
可以用 od 文件名 查看二进制文件
1.2.4 链接
1. 链接找到依赖的库文件(静态与动态),将目标文件链接为可执行程序。
$ gcc [目标文件] -o [可执行程序] -l[动态库名]
假如没有动态库的话(一般)
直接
$ gcc [目标文件] -o [可执行程序]
例如
gcc test.o -o test
链接生成的test文件也是二进制文件。
如果想要执行该程序使用该命令:
$ ./test
执行结果:
1.3 gcc 与 g++
关于对 gcc 和 g++ 很多人的理解都是比较片面的或者是对二者的理解有一些误区,下边从三个方面介绍一下二者的区别:
1.在代码编译阶段(第二个阶段):
- 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 C++ 程序
- 后缀为.cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
- g++ 会调用 gcc,对于 C++ 代码,两者是等价的,也就是说 gcc 和 g++ 都可以编译 C/C++ 代码
2.在链接阶段(最后一个阶段):
- gcc 和 g++ 都可以自动链接到标准 C 库
- g++ 可以自动链接到标准 C++ 库,gcc 如果要链接到标准 C++ 库需要加参数 -lstdc++
3.关于 __cplusplus 宏的定义
- g++ 会自动定义__cplusplus 宏,但是这个不影响它去编译 C 程序
- gcc 需要根据文件后缀判断是否需要定义 __cplusplus 宏 (规则参考第一条)
综上所述:
- 不管是 gcc 还是 g++ 都可以编译 C 程序,编译程序的规则和参数都相同
- g++ 可以直接编译 C++ 程序, gcc 编译 C++ 程序需要添加额外参数 -lstdc++
- 不管是 gcc 还是 g++ 都可以定义 __cplusplus
# 编译 c 程序
$ gcc test.c -o test # 使用gcc
$ g++ test.c -o test # 使用g++
# 编译 c++ 程序
$ g++ test.cpp -o test # 使用g++
$ gcc test.cpp -lstdc++ -o test # 使用gcc
2. gdb调试器
gdb 是由 GNU 软件系统社区提供的调试器,同 gcc 配套组成了一套完整的开发环境,可移植性很好,支持非常多的体系结构并被移植到各种系统中(包括各种类 Unix 系统与 Windows 系统里的 MinGW 和 Cygwin )。
此外,除了 C 语言之外,gcc/gdb 还支持包括 C++、Objective-C、Ada 和 Pascal 等各种语言后端的编译和调试。 gcc/gdb 是 Linux 和许多类 Unix 系统中的标准开发环境,Linux 内核也是专门针对 gcc 进行编码的。
GDB 是一套字符界面的程序集,可以使用命令 gdb 加载要调试的程序。 下面为大家介绍一些常用的 GDB 调试命令。
2.1 GDB命令
命令 简写形式 说明
backtrace bt、where 显示backtrace
break b 设置断点
continue c、cont 继续执行
delete d 删除断点
finish 运行到函数结束
info breakpoints 显示断点信息
next n 执行下一行
print p 显示表达式
run r 运行程序
step s 一次执行一行,包括函数内部
x 显示内存内容
until u 执行到指定行
其他命令
directory dir 插入目录
disable dis 禁用断点
down do 在当前调用的栈帧中选择要显示的栈帧
edit e 编辑文件或者函数
frame f 选择要显示的栈帧
forward-search fo 向前搜索
generate-core-file gcore 生成内核转存储
help h 显示帮助一览
info i 显示信息
list l 显示函数或行
nexti ni 执行下一行(以汇编代码为单位)
print-object po 显示目标信息
sharelibrary share 加载共享的符号
stepi si 执行下一行
2.2 创建测试代码
1.程序的发布方式有两种,debug模式和release模式,Linux默认是release模式,VS模式是debug模式。
2.Linux gcc/g++出来的二进制程序,默认是release模式
3.要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项
vim test.c
gcc -g -o test.exe test.c
写完代码后,gcc编译代码(PS. 如果gcc编译的时候没有加上-g参数,那么就不会保留调试参数,就不能用gdb调试)
测试代码如下:
#include<stdio.h>
#include<stdlib.h>
int main( int argc , char *argv[] )
{
int a = 1;
int i = 0;
int b[3] = {0,1,2};
for(i = 0; i < 3;i++)
b[i] = b[i] + 1;
printf("%d\n",a);
int *p;
p = b;
printf("%d\n",p[0]);
return 0;
}
2.3 启动gdb
在 Linux 操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为 core 文件,Linux 系统所具备的这种功能又称为核心转储(core dump)。幸运的是,GDB 对 core 文件的分析和调试提供有非常强大的功能支持,当程序发生异常崩溃时,通过 GDB 调试产生的 core 文件,往往可以更快速的解决问题。
- 查看是否开启 core dump 这一功能
ulimit -a
如果 core file size(core 文件大小)对应的值为 0,表示当前系统未开启 core dump 功能
- 开启 core dump
ulimit -c unlimited
unlimited 表示不限制 core 文件的大小
测试代码:
#include <stdio.h>
int main()
{
char* p = NULL;
* p = 123;
return 0;
}
测试结果:
[root@]# vim main.c
[root@]# gcc -g -o main.exe main.c
[root@]# ls
main.c main.exe test.c test.exe
[root@]# ./main.exe
Segmentation fault (core dumped)
[root@]# ls
core.17313 main.c main.exe test.c test.exe
测试代码中,发生内存访问错误,程序崩溃,崩溃信息存入core.17313文件中
- 启动gdb
gdb test.exe
2.4 设置断点
- 根据行号设置断点
第一种 (gdb) b 5
第二种 (gdb) b test.c:5
- 根据函数设置断点
(gdb) b main
- 根据条件设置断点
(gdb) b test.c:10 if a == 1
- 根据偏移量设置断点
(gdb) b +12
- 根据地址设置断点
(gdb) b *0x40059b
- 设置临时断点
临时断点只生效一次
(gdb) tbreak test.c:12
显示所有断点
(gdb) info break
清除断点
清除某个断点 (gdb) delete 4
清除所有断点 (gdb) delete
清除当前行断点
(gdb) clear
2.5 运行
- 运行和继续
(gdb) r
继续单步调试(gdb) n
继续执行到下一个断点 (gdb) c
2.6 打印变量的值
- 打印变量
(gdb) p a
- 打印指针
(gdb) p p
- 打印main函数中的变量a
(gdb) p 'main'::a
- 打印指针指向的内容,@后面跟的是打印的长度
(gdb) p *p@3
(gdb) run
Starting program: /root/daizhh/test.exe
Breakpoint 1, main (argc=1, argv=0x7fffffffe508) at test.c:11
11 printf("%d\n", a);
(gdb) p a
$1 = 1
(gdb) p b
$2 = {1, 2, 3}
(gdb) n
1
13 p = b;
(gdb) c
Continuing.
Breakpoint 2, main (argc=1, argv=0x7fffffffe508) at test.c:14
14 printf("%d\n", p[0]);
(gdb) p p
$3 = (int *) 0x7fffffffe400
(gdb) p 'main'::a
$4 = 1
(gdb) p *p@3
$5 = {1, 2, 3}
- 设置变量打印
(gdb)set $index = 0
(gdb)p p[$index]
(gdb) set $index = 0
(gdb) p p[$index]
$6 = 1
- 设置打印格式
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
a 按十六进制格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量
(gdb) p/x a(按十六进制格式显示变量)
(gdb) p/x a
$7 = 0x1
2.7 退出gdb
(gdb) q