Liunx上的C程序与库文件编译调试

编译链接

在这里插入图片描述

预编译阶段

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打印变量valp &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”在单步执行的时候,只有当前线程会执行;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值