现在来看一下上面各个结果: 预处理 这是测试文件代码
接下来我们用gcc编译器来运行一下,当然不是要完全编译,而是先让他预处理一下:
我们用的指令是:
gcc -E test.c -o test.i -E 从现在开始进行程序翻译到预处理工作完成就停下来 -o 指明形成临时文件的名称(这里是test.i,正常来说预处理完成之后生成的就是后缀为.i的文件)
然后打开test.i来看一下:
左边是test.c的内容,是我写的,右边是预处理之后的内容,上面的这些内容是头文件展开的内容,其实也就是将库中的内容拷贝过来。
看右边这里和左边对比,注释那行没了,宏被替换了,条件编译也进行了。
这是刚才程序的结果。 这里甚至可以在外部进行定义:
注意:头文件等等都是在你安装编译器的时候一同被安装到编译器的跟目录下,并且头文件最重要的意义就是支持写代码与自动补齐。 编译
-S 从现在开始完成编译工作,再到全部变成汇编语言停下来。
这就是编译之后的临时文件的内容。 汇编
-c 进行程序翻译做完汇编工作,变成可重定向目标二进制之后停下来。
这里我们已经看不懂了,想看二进制只能od test.o
虽然说这已经是二进制了,但是也不能被执行。 链接 链接就不需要选项了:
a.out就是可执行文件了,如果你不喜欢这个文件的名字可以用-o选项。
这个过程就是将我们的代码和C标准库中的代码结合起来形成可执行二进制程序。
首先要清楚一件事,我们再写代码时用的函数是调用函数,有些时候是调用库函数,我们自己本身没有实现函数的定义。 C标准库是别人写好的代码给我们使用,只有链接的过程才让我们的代码和库里的关联起来。 链接分为动态和静态。 动态链接 在初中的时候,我家的电脑配置很差劲,几乎玩不了什么游戏,只能玩个植物大战僵尸,不过我当时已经玩腻了,想玩玩其他游戏,但是家里电脑配置并不够,后来有年龄比较大的孩子告诉我去网吧,所以就偷偷去网吧玩。
那么从家里走路需要时间。 在链接的这个阶段,我本身就相当于程序一样,家里电脑不行就要去网吧玩,就相当于你调用的库函数不是你写的只能去调用库里面的函数,告诉我网吧这个地方的大孩子就相当于编译器内部的链接器,在他告诉我的过程就等于在和库进行链接,网吧就是所谓的C标准库,这种库叫做动态库。 动态链接是调用某个函数有某个函数的地址,调用的时候就读取地址然后跳转到地址处。 静态链接 后来我还是觉得去网吧太麻烦,也不安全,所以就和家长商量,给我换了一台高配致电脑。从此就没怎么去过网吧了。 那么这就相当于静态链接: 链接时不产生关联,而是将要使用的内部程序拷贝一份到我的程序里面。 动、静太库的优缺点 那么再来考虑一下,去网吧会有很多麻烦: 1.走路需要时间 2.网吧被查封 3.网吧配置升级 查封或者是升级配置肯定是用不了网吧的电脑了,走路的时间也不短。 好处是人带着一点钱过去就可以了,不用做其他的事情。 那么动态链接的缺点就是耗费时间,但是节省空间。 在家里玩电脑确实很方便,不用走路不用担心网吧被查封,也不用担心网吧配置升级玩不了。 但是家里多了高配置的电脑就需要占空间,电脑桌,电脑椅,显示器,机箱,还有线路还要保养等等。 静态链接的好处是不受库的升级和删除,也节省时间,坏处就是占内存。
首先在一个文件里面写这段程序:
那么怎么区分我写的这段程序是静态还是动态呢? 首先编译一下上面的代码:
然后用file 加上test.s就能查看时动态还是静态链接了:
红色框中的叫做动态链接,括号后面的叫做共享库。 在linux下默认生成可执行程序是动态链接。 那么如何查看链接的是哪个库呢?
括号是库的地址。 在linux下库的命名:
动态库:libxxxxx.so 静态库:libyyyyy.a 他们的命名就是去掉后缀和前缀lib
刚才查看的库就是:
c标准库。 linux下默认的是使用动态链接。 如果想用静态链接就需要编译后面加上-static:
静态链接的内存也是非常之大,再用ldd去看的时候就会告诉我们不是靠动态链接执行的。 动态链接 一个程序中多次使用了同一个动态库,这时候不会重复这个动态库,而是只有一个,这也叫做共享库。 所以下载动态链接的c程序就不用再去下载c标准库。 静态链接 静态链接拷贝的时候并不是从动态库中拷贝的,是从静态库中拷贝的。 一般系统是自带动态库,静态库不一定自带。 注意:动态链接只能去动态库找,静态链接只能去静态库拷贝。 系统为了支持编译,提供了标准库.h(这个是告诉我们如何使用),还有动态静态库.so/.a(具体定义,调用,想实现找它们)。 也就是说我们的程序是去.h找静动态链接到别人的库最后才能形成可执行程序。
windows下的动态库和静态库后缀 动态库:.dll 静态库:.lib
make是一个命令,makefile是一个文件。 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的 规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂 的功能操作。 makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编 译,极大的提高了软件开发的效率。
首先需要先创建makefile这个文件,必须叫这个名字,第一个M可以大写。
然后用vim打开它进行输入内容: 因为之前test.c中有c语言代码打印hello world 所以我们在makefile中这样写:
保存退出之后直接用make命令执行这个文件中的内容:
这时候就发现直接执行了用gcc编译test.c文件。
也就是说,只要输入make指令,makefile文件中所有的内容都会被执行。 make/Makefile使用原理 makefile是为了构建项目的两个关系。 依赖关系 test.s的来源是从test.c而来,那么test.s就是依赖于test.c。 makefile文件中第一行
test.s : test.c 意思就是test.s依赖于test.c
依赖方法 test.c如何与test.s有依赖关系呢? 第二行就是依赖方法。 依赖方法必须以tab键开头,不能是四个空格键。 后面就是你实现依赖关系的依赖方法的操作。
如果想删除test.s也可以在makefile文件里实现:
这里依赖关系的clean右边什么都没有就代表没有依赖关系。 但是下面的tab键开头的还是依赖方法。
如果输入指令make只默认执行第一行的依赖关系和依赖方法,所以想删除需要指定是clean才可以。 当然如果没有删除test.s这个文件再次使用make就会告诉我们这是最新的文件了。
因为这是相同的文件内容,里面没有被修改过,没必要进行再次执行。 如果想强制执行怎么办呢?
.PHONY:test.s .PHONY:clean 这两个就是伪目标
保存退出之后再来看看:
伪目标就是用关键字.PHONY:被修饰的对象是为目标,它总会被执行。
那么gcc是如何知道文件不需要这个文件在进行编译了呢?
首先查看文件的时间信息:
Access :最后一次文件被访问的时间 Modify :最后一次文件被修改的时间 Change:最后一次文件属性改变的时间
这里要说一下被访问的时间不会被访问一次就刷新,因为访问文件的次数是最多的,所以就变成了访问多少次之后才会对这个时间进行修改。 gcc是通过Modify这个时间来判断文件需否需要重新编译的。
这里用touch重置了test.c,文件里的内容没有被修改,但是时间都被修改了,gcc就能进行重新编译了。
如果在makefile中写入这样的内容呢?
这时一个依赖关系列表。 当我们用指令make时会发现:
makefile里面的命令是倒过来的。 虽然是从上到下扫描,不过这里第一条依赖关系并没有test.o这个文件所以就要去下面找,最后就导致这些指令是从下到上实现。 在makefile文件中运行的机制就是这样的,像栈一样,先进后出,后进先出。 这就是makefile的推导过程。