GCC编译器和GDB调试器

GCC编译器

GCC 编译器支持编译 Go、Objective-C,Objective-C++,Fortran,Ada,D 和 BRIG(HSAIL)等程序。

实际使用中,使用 gcc 指令编译 C 代码,使用 g++ 指令编译 C++ 代码。

编译过程

例如 g++ test.cpp -o test 可拆解为以下步骤:

  1. 预处理:处理以 # 开头的预处理命令

    1
    2
    3
    # -E 选项指示编译器仅对输入文件进行预处理
    # test.i 文件将头文件及宏定义具体内容全部展开,其余保持不变
    g++ -E test.cpp -o test.i // 生成.i文件
  2. 编译:翻译成汇编文件

    1
    2
    3
    4
    # -S 编译选项告诉 g++ 在为 C++ 代码产生了汇编语言文件后停止编译
    # g++ 产生的汇编语言文件缺省拓展名是 .s
    # test.s 汇编语言文件内容为汇编指令
    g++ -S test.i -o test.s
  3. 汇编:将汇编文件翻译成可重定位目标文件

    1
    2
    3
    4
    # -c 选项告诉 g++ 仅把源代码编译为机器语言的目标代码
    # 缺省时 g++ 建立的目标代码文件有一个 .o 的拓展名
    # test.o 为机器语言识别的二进制代码
    g++ -c test.s -o test.o
  4. 链接:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件

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

    静态链接和动态链接

    动态库一般都会存在/usr/lib/ 目录下;而静态库可以在任何目录下,只要你第一次链接的时候,用绝对路径去链接就行了,之后再删除,是不会影响你的生成的执行文件的。

当然动态库和静态库可以放置到你想放的任何地方,只是动态库需要设置环境变量,而静态库链接的时候需要绝对路径。

静态连接:
源文件中包含的头文件和程序中使用到的库函数,如stdio.h中定义的printf()函数,在libc.a中找到目标文件printf.o(这里暂且不考虑printf()函数的依赖关系),然后将这个目标文件和我们hello.o这个文件进行链接形成我们的可执行文件。

可以发现静态运行库里面的一个目标文件只包含一个函数,如libc.a里面的printf.o只有printf()函数,strlen.o里面只有strlen()函数。因为如果很多函数都放在一个目标文件中,很可能很多没用的函数都被一起链接进了输出结果中,十分浪费空间。

缺点是浪费空间,因为每一个可执行程序都需要堆关联的静态库目标文件有一份拷贝,因此同一个目标文件可能在内存里就有多次拷贝。一旦代码修改,也得重新进行编译。
优点就是可执行程序已经具备了这些库文件,因此运行时的速度更快。

动态连接:
简单来说就是目标文件不会在链接阶段链接,而是推迟到了运行时,检查相关目标文件是否已经存在于内存之中,即共享一份副本。

也就是生成的可执行文件并没有要链接的内容,代码运行后再去找,这时候如果动态库被删除,就无法成功运行。

优点是空间占用更少,缺点是运行时效率降低,但一般不超过5%,因此可以忽略。

g++重要编译参数

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

    1
    2
    3
    # -g 选项告诉 GCC 产生能被 GNU 调试器 GDB 使用的调试信息,以调试程序。
    # 产生带调试信息的可执行文件 test(不加则 test 不包含调试信息)
    g++ -g test.cpp -o test
  2. -O[n] 优化源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 所谓优化,比如省略代码中从未使用过的变量,直接将常量表达式用结果值来替代等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。
    # -O 选项告诉 g++ 对源代码进行基本优化。这些操作在大多数情况下都会使程序执行的更快。-O2 选项告诉 g++ 产生尽可能小和尽可能快的代码。
    # -O 同时减小代码长度和执行时间,效果等价为 -O1
    # -O0 表示不做优化
    # -O1 默认优化
    # -O2 除了完成 -O1 的优化之外,还进行一些额外的调整工作,如指令调整等
    # -O3 包括循环展开和其他一些与处理特性相关的优化操作
    # 选项将使编译的速度比使用 -O 时慢,但通常产生的代码执行速度会更快
    # 可以用如 "time ./test" 来查看可执行文件的运行效率,比较优化效果
    g++ -O2 test.cpp
  3. -l 和 -L 指定库文件 | 指定库文件路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # -l 参数就是用来指定程序要链接的库,-l 参数紧接着就是库名
    # 在 /lib 和 /usr/lib 和 /usr/local/lib 里的库可以直接用 -l 来进行链接

    # 链接 glog库
    g++ -lglog test.cpp

    # 如果库文件没放在上面三个目录里,需要使用 -L 参数指定库文件所在目录
    # -L 参数紧接着的是库文件所在的目录名

    # 链接 mytest 库,libmytest.so 在 /home/Test 目录下
    g++ -L/home/Test -lmytest test.cpp
  4. -I 指定头文件搜索目录

    1
    2
    # 若头文件在 /usr/include 目录下一般是不需要指定的,否则就需要 -I 参数来指定了,比如头文件放在 /myinclude 目录里,否则会报错 “xxxx.h: No such file or directory” 。
    g++ -I/myinclude test.cpp
  5. -Wall 打印警告信息 | -w 关闭警告信息

    1
    2
    3
    4
    5
    # 打印出 gcc 的警告信息
    g++ -Wall test.cpp

    # 关闭所有警告信息
    g++ -w test.cpp
  6. -std=c++11 设置编译标准

    1
    2
    # 使用 c++11 标准编译 test.cpp
    g++ -std=c++11 test.cpp
  7. -o 指定输出文件名
    1
    2
    3
    # 指定即将产生的文件名
    # 指定输出可执行文件名为test
    g++ test.cpp -o test
  8. -D 定义宏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 在使用gcc/g++编译的时候定义宏
    # 常用场景:
    # -DDEBUG 定义 DEBUG 宏,可能文件中有DEBUG宏部分的相关信息,用 DDEBUG 来选择开启或关闭 DEBUG
    # 举例:
    // -Dname 定义宏 name,默认定义内容为字符串 “1”
    #include <stdio.h>
    int main()
    {
    #ifdef DEBUG
    printf("DEBUG LOG\n");
    #endif
    printf("in\n");
    }
    // 1. 在编译的时候,使用g++ -DDEBUG main.cpp
    // 2. 第七行代码可以被执行

    GDB调试器

GDB(GNU Debugger) 是一个用来调试C/C++程序的功能强大的调试器,是 Linux 系统开发 C/C++ 最常用的调试器。

GDB主要功能:

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

调试开始:执行gdb [filename] ,进入gdb调试程序,其中 filename 为要调试的可执行文件名。

编译程序时需要加上 -g,之后才能用 gdb 进行调试:g++ -g main.cpp -o main
回车键:重复上一命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$(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())
作者

Benboby

发布于

2020-12-20

更新于

2021-03-01

许可协议

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×