我们都知道,源文件要想得到一个可执行程序,必须经历以下四个阶段:
在集成开发环境(IDE)中,这四步通常被封起来了,一般我们执行一段代码就会直接默认执行了这四步。
那么在linux下,gcc该如何完成呢?
- 预处理结束后,在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作。
- 在检查无误后,gcc 把代码翻译成汇编语言。
- 用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,仅生成汇编代码。
- 实例: gcc –S hello.i –o hello.s
- 汇编阶段是把编译阶段生成的“.s”文件转成目标文件
- 读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了
- 实例: gcc –c hello.s –o hello.o
- 在成功编译之后,就进入了链接阶段。
- 实例: gcc hello.o –o hello
链接这里涉及一个概念:函数库
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么是在哪里实现的“printf”函数呢?
答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。
而且函数库一般分为两种:静态库与动态库
- 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,其后缀名一般为“.a”。
- 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行与运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
总结:
一旦写好,只需要一个make命令,整个工程完全自动编译
,极大的提高了软件开发的效率。那我们就先来见一见make与makefile的使用:
首先要创建一个文件,名字可为makefile / Makefile,而且要事先准备好一份待编译的代码
下面我们来分析一下语法:
既然有构建,那就需要清理
此时clean的依赖列表为空
但是,此时多make几次你就会发现一个问题,他会提示你可执行程序已经是最新了,不支持编译了,这是为什么呢?
这是make的一种机制,由于在大型的工程中,代码编译一次的时间可能很长,如果你的源文件没有修改,那就没有必要再次编译,所以在make执行前会比较文件的时间。
可使用stat + 文件名查看文件的时间。
- Modify表示文件内容的修改时间
- Change表示文件属性的修改时间
所以,为了每次都可以编译,我们可以这样写:使用PHONY
,后面跟一个伪目标名,该命令会让伪目标对应的方法总是被执行。
但我们一般不会对构建时设置PHONY,都是对清理时设置。
然而由于rm本身就不关心时间,所以写不写的效果都一样。但由于依赖方法可以是任意行指令,所以除了rm,clean后面可能还有其它命令,所以一般都加上。
由于make命令会回显命令,所以可以在命令前+@关闭回显。
注意:
到这里,我们就可以使用简单的make、makefile了。
但是如果要完整的写出编译过程。我们并不是使用demo.c源文件直接形成demo可执行文件的。
我们都是demo.c->demo.i->demo.s->demo.o->demo
其推导过程类似于栈,直到有依赖文件后就到栈的出口了。
但是平常我们也不会按照这种方法写,因为太麻烦了,我们一般会先生成 .o文件,然后再使用.o直接生成可执行文件
为了书写方便,makefile中引入了新的特殊符号:%、$、<
在Makefile中,也可以定义变量
执行make就会自动替换掉特殊符号
此时如果想改变形成的可执行程序名,可直接修改bin。
但我们的Makefile依然不是最终版本,src不想使用.o了,太麻烦,如果我们要用源文件形成目标文件该怎么写呢?
此时我们就可以使用一批源文件生成目标文件了
但如果我们要形成多个可执行程序呢?
假如我有两个.c文件,它们都包含main函数,那该怎么办呢?–定义多个变量呗
但是由于make默认只形成一个可执行,所以我们要采取特殊的方法
我们定义一个伪目标,该伪目标的依赖关系是两个可执行文件,没有依赖方法;因此makefile就会自动推导两个依赖关系,依次执行它们依赖方法,此时就达到了形成两个可执行的目的。
因此,以后再对代码进行编译时,可先写一个makefile,就可以简化操作。
同时编译两个.c文件
git clone + 仓库链接
(将git仓库克隆至本地)
git add + 文件名
(将文件添加到git的暂存区)
git commit -m + "文件描述"
(它用于将暂存区中的更改记录为一个新的提交。每次执行 git commit,都会在当前分支上创建一个新的提交节点,这个节点包含了自上次提交以来所做的所有更改。)
将本地仓库的更改推送到远程仓库
git log 命令是 Git 版本控制系统中的一个非常有用的命令,它用于显示提交日志信息。
这个命令可以显示出一个或多个分支上的提交记录,包括每次提交的哈希值、作者、日期和提交信息。
程序的发布方式有两种,debug模式和release模式;Linux gcc/g++出来的二进制程序,默认是release模式。
要使用gdb调试,必须在源代码生成二进制程序的时候, 加上-g选项
若想启动调试,需使用 gdb + 可执行文件名
退出调试,在gdb下输入 q(quit)即可。
默认没有断点时,运行时会从头到尾执行完,若想让代码停下来,需要打断点。
设置断点的三种方式:
:
行号/函数:在指定文件的指定位置打断点
那我设置了断点以后如何查看呢?
查看断点信息:info / i + b
删除断点:delete/d + 断点编号
此时我们会发现,当一个断点删除后,其余断点的编号不会随之改变
。
而且再设置新的断点也不会使用之前的编号,即使所有断点全部被删除了。
如果中途退出了调试,所有的断点信息就没了
。
设置条件断点
断点设置好以后,此时再次运行程序会跳至第一个断点处
在调试过程中,我们如何查看变量信息呢?
若想查看变量信息:
但此时在运行别的语句时,变量的信息不会一直显示,只有你输入 p + 变量名时才显示,那如何让变量一直显示呢?
此处的变量编号也不会被再次使用,除非退出再重新调试
取消常显示
直接运行至指定位置:until + 行号
运行完整个函数:finish
直接运行至下个断点处
有时我们想让某个断点失效该怎么办呢?
修改变量的值:set var 变量名 = 新的值
在调试时,可使用set var对代码就行预修正(即调试时就修改变量的值,无需退出调试修改代码)
breaktrace:查看各级函数调用及参数(类似于vs中的调用堆栈)
info / i + locals:查看当前函数内局部变量的值。
watch + 变量名:但该变量发生改变时,就会有提醒。