编译链接
预编译阶段
a) 删除所有的“#define”,并且展开所有的宏定义;
b) 处理所有的条件预编译指令,“#if”、“#ifdef”、“#endif”等;
c) 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置;
d) 删除所有的注释;
e) 添加行号和文件名标识,以便于编译器产生调试用的符号信息及编译时产生编译错
误和警告时显示行号;
f) 保留所有的#pragma 编译器指令,因为编译器需要使用它们。
编译阶段
词法分析、语法分析、语义分析,代码优化,汇总符号。
汇编阶段
将汇编指令翻译成二进制格式,生成各个 section,生成符号表。
链接阶段
a) 合并各个 section,调整 section 的起始位移和段大小,合并符号表,进行符号解析,
给符号分配虚拟地址
b) 符号重定位
单文件
c++程序也可以使用gcc编译运行(有的库不一样或没有),但不如使用g++
sudo apt install gcc #首先得安装个gcc
gcc -E mian.c -o main.i #预编译
gcc -S mian.i -o main.s #编译
gcc -c mian.s -o main.o #汇编
gcc mian.o -o main.i #链接
gcc main.c -o main #也可以一步到位
#-o 是指定输出文件,当然也可以没有,就没有输出
你想要那些文件,就使用那个参数,gcc会自己识别后缀,执行你之前没执行的步骤
多文件
同理,先将多个文件独立汇编
最后在链接
# 比如有个a.c b.c
gcc -c a.c -o a.o
gcc -c b.c -o b.0
gcc a.o b.o -o ab
#或者
gcc a.c b.c -o ab
如果b中使用的部分函数是在a中声明定义的怎么办(编译时会报错)?那就是使用头文件,将a的方法声明在a.h,b.c在编写时将其包含进去。我们编译链接时就不会报错了。(b.h,要和a.c同目录,不然就使用-l指定目录,当然还有其他方法)
若经常使用b的话,可以将其构建成为库文件
库文件
库是一组预先编译好的方法的集合。Linux系统存储的库的位置一般在:/lib 和 /usr/lib。在 64 位的系统上有些库也可能被存储在/usr/lib64 下。库的头文件一般会被存储在/usr/include 下或其子目录下。
库有两种,一种是静态库,其命令规则为 libxxx.a,一种是共享库,其命令规则为 libxxx.so
静态库和共享库的区别
静态库在链接时将用到的方法包含到最终生成的可执行程序中,而共享库不包含,只做标记,在运行程序时,才动态加载
静态库生成
第一步:先将需要生成库文件的所有“.c“文件编译成“.o”文件
第二步:使用 ar 命令将第一步编译的所有”.o”文件生成静态库,其中:
◼ c 是创建库
◼ r 是将方法添加到库中
◼ v 显示过程
动态库生成
第一步:先将需要生成库文件的所有“.c“文件编译成“.o”文件
第二步:使用 gcc 命令将第一步编译的所有”.o”文件生成共享库
库的使用
如果库的位置不在/usr/lib或者/usr/lib64时,在编译的时候就需要使用(静态/动态可库都要)
◼ -L 指定库的存储路径
◼ -l 指定库的名称(不需要前面的‘lib’和扩展名‘.a’)
如果在库的存储路径有同名的共享库和静态库,gcc 默认使用共享库
静态库到这就没问题了,但动态库还有些问题,在执行时可能会出错,原因是系统加载共享库时,找不到对应的共享库文件”libfoo.so”, 但是该库确实在当前目录下存在。这是为什么呢?因为系统默认只会去存储库的标准位置(/lib 或/usr/lib 等)加载,而不会在当前位置寻找。所以将库拷贝到/usr/lib 下,再执行程序,就可以成功。(或者修改环境变量)。
ldd
使用ldd可以查看该程序使用了那些动态库。
gdb调试
gdb 调试的可执行程序的Debug版本,gdb 可执行文件名 #即可进入调试
Debug 版本和 Release 版本
Debug 版本
Debug 版本为可调式版本,生成的可执行文件中包含调试需要的信息。我们作为开发人员,最常用的就是 debug 版本的可执行文件。
Debug 版本的生成:
因为调试信息是在编译过程时加入到中间文件(.o)中的,所以必须在编译时控制其生成包含调试信息的中间文件。(使用 -g 参数)
gcc -c main.c -g # 生成包含调试信息的中间文件
#或者
gcc -o main main.c -g
Release 版本
Release 版本为发行版本,是提供给用户使用的版本。用 gcc 默认生成的就是 Release 版本。
单进程,线程调试命令
命令 | 作用 | 命令 | 作用 |
---|---|---|---|
r(run) | 开始运行 | n(next) | 单步执行(不进入函数) |
l(list) n | 显示第n行的上下文 | s(step) | 单步执行(会进入函数体内) |
b(break) n | 在第n行打断点 | finish | 跳出函数 |
p(print) val | 打印变量val | p &val | 打印val的地址 |
p arr(数组名) | 打印数组中所有变量的值 | info break | 打印断点信息 |
c(continue) | 继续执行到下一个断点 | bt | 显示函数调用栈 |
until n | 运行到第n行 | ptype val | 显示变量类型 |
[Tab] | 自动补全 | [Enter] | 再次执行上次的命令 |
q(quit) | 退出 |
多进程调试
set follow-fork-mode child(parent)
未被跟踪的进程会被直接结束掉。
多线程调试
info threads #显示线程信息,包括给他分的id
thread id #调试指定线程
set scheduler-locking off|on|step
“off ”表示不锁定任何线程;
“on”只有当前被调试的线程继续运行;
“step”在单步执行的时候,只有当前线程会执行;