本文操作均在 Linux 环境下完成
目录
GCC
要了解 gcc 的使用先要了解源代码 (.c) 文件生成可执行文件的过程,分为两个环境:
具体参考:https://blog.csdn.net/m0_46606290/article/details/120604689
程序的执行分为翻译环境和运行环境:
这里 gcc 完成的是翻译环境:
(1):预处理
(2):编译
(3):汇编
(4):链接
1.创建源文件
首先在当前目录下新建一个新目录并且切换到该目录下(mkcd),直接用 vim 在新目录中打开一个新的 hello.c 文件,写入后 wq 保存退出并展示(cat)hello.c 的内容:
关于 vim 的详细用法:http://c.biancheng.net/view/805.html
源文件已经生成,下面使用 gcc 操作将逐渐把它变成可执行的文件:
2.预处理(-E)
使用 gcc -E hello.c 命令将源文件的头文件展开:
发现头文件展开后的文件直接显示在终端上,而发现目录下并没有 hello.i 文件,这是因为默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。因此该指令往往会和 -o 选项连用,将结果导入到指令的文件中。比如:
gcc -E hello.c -o hello.i
发现成功生成 .i 文件:
注意,这里的生成的 hello.i 文件是自定义名称,比如:
命名为 hello1.i 也会生成和 hello.i 内容一样的预处理文件。
在此命令中加上 -C 指令会生成带注释的预处理文件(此处只让其显示而并不替换掉原 hello.i 文件):
(因为带注释的预处理文件太长所以展示下半部分)
3.编译(-S)
gcc -S hello.i
令 gcc 仅将指定文件加工至编译阶段,并生成对应的汇编代码文件并且默认保存在当前文件下。例如:
还可以为 gcc -S 指令添加 -o 选项,令 gcc 将编译结果保存在我们指定的文件中:
到这里可以看出来,实际上 -o 就是指定前方指令生成的文件应该保存在哪一个文件下
这里也可以直接对 hello.c 源文件进行 -S 编译操作,gcc 依旧会把程序运行到编译阶段,直接生成 .s 汇编文件,虽然不会生成中间的 .i 预处理文件,但实际上 gcc 也完成了预处理的操作
对于生成的汇编文件也可以加上注释,使用以下命令:
gcc -S hello.c -fverbose-asm
(生成了汇编注释)
4.汇编:(-c)
通过为 gcc 指令添加 -c 选项(注意是小写字母 c),即可让 gcc 将指定文件加工至汇编阶段,并生成相应的 .o 目标文件。例如:
同样,还可以在 gcc -c 指令后添加一个 -o 选项,用于将汇编操作的结果输入到指定文件中(在此不展示)
和 gcc -S 类似,gcc -c 选项并非只能用于加工 .s 文件。而是令 gcc 将指定文件加工至汇编阶段,但不执行链接操作。比如:
- 如果指定文件为源程序文件(例如 hello.c),则 gcc -c 指令会对 hello.c 文件执行预处理、编译以及汇编这 3 步操作;
- 如果指定文件为刚刚经过预处理后的文件(例如 hello.i),则 gcc -c 指令对 hello.i 文件执行编译和汇编这 2 步操作;
- 如果指定文件为刚刚经过编译后的文件(例如 hello.s),则 gcc -c 指令只对 hello.s 文件执行汇编这 1 步操作。
操作如下:
注意,如果指定文件已经经过汇编,或者 gcc 无法识别,则 gcc -c 指令不做任何操作。
5.链接(-O)
前面已经提到,gcc -o
用来指定输出文件,如果不使用 -o 选项,那么将采用默认的输出文件。例如默认情况下,生成的可执行文件的名字默认为 a.out :
( a.out 和 hello.out 内容相同)
链接格式为: gcc [输入文件] -o [输入文件]
上列 gcc hello.c -o hello.out 相当于:将源文件( hello.c )作为输入文件,将可执行文件( hello.out )作为输出文件,完整地编译了整个程序
6.一步到胃:
也可以指定( -o ):
7. gcc -l 选项:手动添加链接库
vim 修改 hello.c 为如下内容:
发现错误提示为 pow 为定义,说明 <math.h> 库需要手动进行链接,此时就需要用上 -lm :
(链接成功)
GDB
gdb : Linux 下使用最多的一款调试器(Debugger)
借助 gdb 调试器可以实现以下几个功能:
(1): 程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;
(2):可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;
(3):程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。
gdb 的主要功能就是监控程序的执行流程。所以,只有当源程序文件编译为可执行文件并执行时,gdb 才会派上用场。
以 hello.c 源文件为例,其内容为:
正常情况下,使用 gcc 编译该源代码的指令如下:
可以看到,这里已经生成了 hello.c 对应的执行文件 hello.exe,但此文件不支持使用 gdb 进行调试。
注意,仅使用 gcc 命令编译生成的可执行文件,是无法借助 gdb 进行调试的。
原因很简单,使用 gdb 调试某个可执行文件,该文件中必须包含必要的调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等),而上面生成的 hello.exe 则没有。
这里只需要使用 gcc -g 指令编译源文件,即可生成满足 gdb 要求的可执行文件。
下面仍以 hello.c 源程序文件为例:
(不用删除上次操作产生的 hello.exe,直接执行 -g 会自动覆盖之前产生的同名的 .exe 可执行文件)
由此生成的 hello.exe,即可使用 gdb 进行调试。
开始调试
gdb hello.exe :
可以看到,该指令在启动 gdb 的同时,会打印出一堆免责条款。通过添加 --silent(或者 -q、--quiet)选项,可将部分信息屏蔽掉:
启动成功的标志就是最终输出的 (gdb),通过在 (gdb) 后面输入指令,即可调用 gdb 调试进行对应的调试工作。
下面列出了几个最常用的调试指令及各自的作用:
调试指令 作 用
(gdb) break xxx
(gdb) b xxx 在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置。
(gdb) run
(gdb)r 执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue
(gdb) c 当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束。
(gdb) next
(gdb) n 令程序一行代码一行代码的执行。
(gdb) print xxx
(gdb) p xxx 打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list
(gdb) l 显示源程序代码的内容,包括各行代码所在的行号。
(gdb) quit
(gdb) q 终止调试。
如上所示,每一个指令既可以使用全拼,也可以使用其首字母表示。
更多指令使用 (gdb) help 进行查看。
下列展示部分用法:
(gdb) l ==> 显示带行号的源代码:
默认情况下,l 选项只显示 10 行源代码,若需查看后续代码,按 Enter 回车即可
(gdb) b 7 ==> 在第 7 行源代码处打断点:
(gdb) r ==> 运行程序,遇到断点停止:
(gdb) p n ==> 查看代码中变量 n 的值:
当前 n 的值为 1,$1 表示该变量所在存储区的名称
(gdb) b 12 ==> 在程序第 12 行处打断点:
(gdb) c ==> 继续执行程序:
(gdb) p n ==> 查看当前 n 变量的值:
当前 n 的值为 101
(gdb) q ==> 退出调试( y 确定):