文章目录
一.引例:
C语言
下面代码可能编不过去
因为编译器的版本没达到,需要手动改版本
默认形成的可执行文件是a.out
也可以更改形成的文件名-o 是object的意思,形成的可执行文件后缀随便加
gcc test.c -o my.exe -std=c99
C++
C++文件后缀不仅仅有.cpp和.cc,还有很少见到的.cxx
gcc不能编译C++的代码,但g++能编译C的代码
g++ test.cpp -o my.exe -std=c++11
二.程序翻译的过程
预处理
预处理把头文件直接展开,10来行变成800多行
Linux里面有库才能使用头文件,并且预处理之后头文件展开的位置能直接看到库存在的位置
预处理阶段宏会被替换,注释也被去掉
条件编译
引例:同样一种软件,有社区版,专业版,免费版。
那么维护代码的时候需要维护几份源代码?
维护一份代码即可,怎么做到的?
以以下代码为例
运行就会发现只打印了一部分
预处理就被裁了
然后再做一下改变,V1随便给个值
#include <stdio.h>
#define V1 1
int main()
{
#ifdef V1
printf("功能1\n");
#elif V2
printf("功能1\n");
printf("功能2\n");
#else
printf("功能1\n");
printf("功能2\n");
printf("功能3\n");
#endif
return 0;
}
运行
其中#ifdef #elif 等就是条件编译
这样裁剪可以定义出不同版本的软件
宏也可以在外面定义
gcc -D V1=1 proj.c -o filename
编译
gcc -S test.i -o test.s -std=c99
很明显,就是汇编语言
汇编
gcc -c test.s -o test.o
就是.obj结尾的文件
内容是形成二进制的目标文件
还是运行不了
链接
gcc test.o -o my.exe
就可以直接运行了
三.链接–动静态链接
链接是什么?
程序与库结合的过程
引例:从零开始写代码,假设设计一个printf函数,设计好了大半个月过去了,效率比较低,所以语言设计者把一些公共的方法抽取出来,放进自己的标准库
下面的指令是查看文件调用了哪些动态库
ldd filename
查看画横线的文件,没形成C语言库之前(就是C语言的源代码),被顶级程序员打包成库
我们怎么知道C标准库里面有什么?所以就有一批对应的头文件,方便调用
所以安装开发环境:安装C标准库 + C头文件
动静态库
为什么要有库?
让开发站在巨人的肩膀
提高开发效率
怎么办?
故事:
故事来到第二阶段:张三去不了网吧了(无法调用动态库),计划不能完整的执行了
第三阶段:
总结:所有动态链接都需要跑到库中执行,然后返回,静态链接不需要
证明:
C动态库,是Linux默认提供的
gcc默认形成的可执行程序,默认采用动态链接
ls命令也是同理
要是删掉的话,就相当于上述故事中的警察查封网吧,大部分指令都运行不了了
静态库的位置,文件以.a结尾
优缺点
下面的指令是查看文件详细信息
file filename
动态库与动态链接的优缺点:
1.不能丢失(网吧不能去了就玩不了)
2.节省资源(网吧的电脑共用)
静态库与静态库的优缺点:
1.一旦形成就与库无关(自己有电脑就不用去网吧了)
2.浪费资源(自己的电脑自己用)
验证:
查看静态库的指令(mytest-static是形成test.c静态库的名称,名字随便取)
gcc -o mytest-static test.c -static -std=c99
会发现运行不了,找不到C语言的静态库
是因为默认情况下,Linux上一般静态库都是默认没有安装的
yum安装即可
sudo yum install -y glibc-static libstdc++-static
对比一下:
文件大小上静态比动态大很多
静态链接的应用场景
提高可移植性,比如直接把二进制代码从这个A机器到B机器,跨平台性提高,不依赖动态库,不用做过多的环境监测
四.make/makefile
原理:
make是一个命令
makefile是一个文件
创建makefile和创建普通文件的命令是一样的
touch makefile
以下代码为例
效果就是下面
关于依赖关系和依赖方法的理解,这里讲一个小故事:
月底了,身为大学生的你没钱了,拨通了父亲的电话:我是你儿子,然后你挂了电话。
我是你儿子这句话就表明了依赖关系,但是目的没达成,所以只有依赖关系没用
然后又打通电话:我是你儿子,没钱了打钱。你父亲就明白了
这就是表明了依赖关系又表明了依赖方法
发现在前面使用的时候,make是运行的第一段,make clean是第二段,同时make mytest也可以运行第一段
因为make是从上面向下运行的
把makefile反过来验证一下:
至于.PHONY是什么意思这里讲解一下:
以下面的makefile文件为例
mytest:test.c
gcc -o mytest test.c
clean:
rm -f mytest
会发现第二次make的时候会说:此文件已经是最新的
但如果更改test.c的内容后,就可以再次make。
因为系统认为test.c已经编译过了,且没有最新的更改,所以没必要执行。
加了.PHONY后就可以一直执行
.PHONY:mytest
mytest:test.c
gcc -o mytest test.c
clean:
rm -f mytest
.PHONY:XXX
表示XXX对应的方法,总要被执行的
一般写makefile的时候,形成可执行程序,源代码没有更新,没有必要编译
清理项目希望是被总是执行的,因为有些文件想要清理干净
为什么makefile对最新的可执行程序,默认不想重新生成呢?
假设一个项目有两千个源文件,很小的一部分需要做改动,如果全部编译效率很慢。
提高编译效率
makefile怎么知道我的程序需要被编译了呢?
源文件的更改时间和可执行程序更改的时间是不一样的
源文件:
可执行文件:
对比可执行文件的最近修改时间和源文件最近的修改时间,判断谁最新?
以此来判断是否需要编译文件
.PHONY的意思是直接编,不用比较
平时在写VS的时候,有时候会出错,清理重新编译后,就可以执行。原因就是不同的环境更新时间的策略不一样。
makefile中的内置符号
make的时候,他会自动替换目标文件
在makefile中注释是#
多个指令的实现也可以
引例:
test.exe:test.o
gcc test.o -o test.exe
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
.PHONY:clean
clean:
rm -f test.s test.i test.o test.exe
假设依赖文件找不到就会报错
重要的一点来了,可以乱序,但最终的目标文件必须是第一个
最终要的test.exe可执行程序并没有形成
makefile也支持变量
bin=mytest
src=test.c
$(bin):$(src)
gcc -o $@ $^
.PHONY:clean
clean:
rm -f $(bin)
就跟宏一样
不想见到执行的命令
加个@就行
又想隐藏程序,又想看到执行的什么命令,可以自己设置
这里echo也是一个命令,所以要加上@
五.gdb调试器
如何证明有调试信息?
readelf -S test.debug
其中有读取的debug信息
过滤一下
再看一下release版本的,没有
Linux下的任何可执行程序都是ELF格式(就是二进制的一种形式)
readelf就是读取可执行程序
使用gdp
下载gdb的指令
yum install -y gdb
为了方便演示,将以以下代码为例:
#include <stdio.h>
int AddToTarget(int start, int end)
{
int i = start;
int sum = 0;
for(; i <= end; i++)
{
sum += i;
}
return sum;
}
int main()
{
printf("run begin...\n");
int result = 0;
result = AddToTarget(1,100);
printf("result: %d\n", result);
printf("run end...\n");
}
进入gdb工作模式
gdb test.debug
quit 退出gdb
总结gdb基础指令
方便查表,先总结一下
功能 | 全称 | 简称 |
---|---|---|
退出gdb | quit | |
查看源代码 | list (文件名:)(行号/函数名) | l (文件名:)(行号/函数名) |
打断点 | b (文件名:)(行号/函数名) | |
查看断点 | info | i |
删除断点 | d 断点编号 | |
使断点关闭 | disable 断点编号 | |
使断点打开 | enable 断点编号 | |
在某个函数开头设置断点 | break 函数名 | |
逐过程(F10) | next | n |
逐语句(F11) | step | s |
查看变量内容或地址 | p | |
显示变量内容或地址(相当于监视) | display 变量或变量地址 | |
删除显示的变量内容或地址(相当于监视) | undplay 变量或变量地址的序号 | |
运行至下一个断点处 | contiune | c |
运行结束所在函数,就停下来 | finish | |
跳转至指定行,中间的代码都是运行完的 | until 行数 | |
查看当前栈帧局部变量的值 | info local | |
修改变量的值 | set var | |
查看调用栈(函数) | bt |
list
list 查看源代码
从头部开始查 list简写成l 跟个行号0
l 文件名:行号也可以,查其它文件也行
gdb会默认记住最近的一条命令的,直接按回车
查行数会发现默认15在中间
也可以查函数
运行与断点
run简写成r
就像VS点以下F5调试,不打断点就直接运行完成
打断点b (文件名:)序号
b (文件名:)函数名
可以看到断点的序号
查看断点info b
去掉断点d加断点序号
先打4个断点,发现断点序号不是从1开始,而是接着上次叠加
运行发现在17行停下来
如果想要禁掉断点,但不删
disable 断点序号
再次运行就是19行,因为18行是空行
打开断点
enable 断点序号
break 函数名:在某个函数开头设置断点
逐过程与逐语句
C语言就是面向过程,F10就是逐过程,遇到函数直接越过去执行完。逐语句就是遇到函数进去
17行已知先打一个断点
逐过程全称next 简写为n
因为18行没东西,所以直接到19行了
并没有进到函数内部
如果想进到函数内部,就是逐语言F11,指令为s
调试过程中我们还想要看到局部变量,也就是监视或自动窗口
想查看i变量print i 简写称p i
查看地址也可以
过于麻烦,需要每次输入
display 可以查看的变量 可以解决
如果想删掉监视的内容
undisplay 监视的序号
区域性的执行
运行至下一个断点处 contiune 简写为c
首先有三个断点
效果如下
假设问题可能在函数里面
finish指令 运行结束所在的函数,就停下来
假如被循环困住了
跳出去的指令为until 指定行
扩展
info local/locals 查看当前栈帧局部变量的值
set var 修改变量的值
bt 查看调用栈
查看函数用的
效果