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 个步骤合并成一个。 先介绍一个每个步骤都分别做了些什么事儿

  1. 预处理:在这个阶段主要做了三件事: 展开头文件 、宏替换 、去掉注释行( 这个阶段需要 GCC 调用预处理器来完成,最终得到的还是源文件,文本格式)
  2. 编译:这个阶段需要 GCC 调用编译器对文件进行编译,最终得到一个汇编文件
  3. 汇编:这个阶段需要 GCC 调用汇编器对文件进行汇编,最终得到一个二进制文件
  4. 链接:这个阶段需要 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.在链接阶段(最后一个阶段):

  1. gcc 和 g++ 都可以自动链接到标准 C 库
  2. 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

posted @ 2023-01-06 11:39  [BORUTO]  阅读(136)  评论(0编辑  收藏  举报