文章目录
- 1 gcc/g++
- 1.1 预处理
- 1.2 编译
- 1.3 汇编
- 1.4 链接
- 1.4.1 静态链接
- 1.4.2 动态链接
- 2 make和makefile
- 2.1 依赖关系
- 2.2 依赖方法
- 2.3 伪目标
- 3 总结
1 gcc/g++
当我们创建一个文件,并向里面写入代码,此时,我们该如何使我们的代码能够运行起来呢?
如果是在windows的vs下,只需要点击运行就行了,现在在Linux下,该如何运行?
我们需要使用gcc编译器来编译已经写好的test.c源文件
编译之后,会在当前目录之下生成可执行程序,运行可执行程序即可
使用红框中的命令gcc test.c -o test
就完成了对源文件的编译。
之后,生成了可执行程序test.
运行test即可执行我们的可执行程序。
补充:c语言的程序用gcc编译,c++的程序用g++编译
- 格式:
gcc - 选项 源文件名 -o 目标文件名- 选项:
-E 从当前文件开始,在预处理完成之后停止, 生成的文件后缀一般加i
-S 从当前文件开始,在编译完成之后停止, 生成的文件后缀一般加s
-c 从当前文件开始,在汇编完成之后停止, 生成的文件后缀一般加o
1.1 预处理
预处理的作用:
- 去除注释
- 展开头文件
- 处理条件编译
- 进行宏替换
命令: gcc -E 文件名.c -o 文件名.i
左边是test.c文件,右边是test.i文件(预处理文件)
1.2 编译
命令: gcc -S 文件名.i -o 文件名.s
功能:将预处理文件编译成汇编语言
1.3 汇编
命令: gcc -c 文件名.s -o 文件名.o
功能:将编译生成的.s文件中的内容转变成机器能识别的二进制机器码。
可以通过od
命令查看.o文件
将二进制机器码以十六进制的形式显示出来。
1.4 链接
到这一步时候,我们的文件是,o的汇编文件。
这里有一个问题:我们写的程序中,使用到了printf()函数,但是我们并没有写printf()函数的实现方法和定义等,为什么执行程序的时候不会报错?
因为我们使用了库函数
printf()函数很明显是库函数。假设我们不包含<stdio.h>头文件,我们的程序必然会报错。
我们包了<stdio.h>文件之后就能使用了吗?
也不是,因为我们知道.h只是一个头文件,相当于有一个函数的声明,但是没有函数的具体定义,函数的具体定义肯定是一个文件。
假设库的创建者写了1000个函数,分为两个文件。其中,函数的实现叫做c和定义叫做c.h。
库的创建者不想给我们看函数的具体实现。所以对这个库进行的封装。但是他必须提供库的使用方法,所以又把头文件给了我们,提供给我们具体的使用方法。
我们通过头文件得知使用方法之后,在我们的c程序执行到库函数时候,就会链接到库中的库函数。库函数也是.o目标文件,将我们自己的程序和库函数链接到一起,就会生成可执行程序.exe。
那么,该如何找到库呢?
在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找。
链接的库分为静态库和动态库。
1.4.1 静态链接
- 当我们的函数执行到库函数的时候,跳转到链接的库函数,将库函数中该函数的实现拷贝到当前程序文件中。
- 再此之后再生成可执行文件。
静态链接的指令gcc 文件名.o -o 文件名 -static
1.4.2 动态链接
- 当执行到库函数时,跳转到动态库中,找到该函数的.o文件,去链接标准库中库函数的.o文件。
- 链接完之后再跳转回来。
通过ldd命令可以查看a.out链接的动态库。
图中的红框就是我们链接的动态库c
库的名称为去掉前缀去掉后缀,图中就是去掉前缀lib,去掉后缀.so,最后得到c
通过file指令查看文件的属性
黄色框中说明这是一个动态链接,链接的是动态库。
动态链接和静态链接的区别是什么?
通过上图可以看出静态链接比动态链接之后的文件大很多。
这是因为动态链接是直接跳转,而静态链接是将静态库中的内容拷贝到文件中
总结:
- 静态库或者动态库一个系统只有一个,所有使用系统的用户都在使用同一个库。
- 静态链接后的可执行程序不会受到静态库升级或者被删除的影响,而动态库会受到影响。
- 系统为了我们编程,提供了标准库的头文件.h,和动静态库.so/.a,头文件是为了告诉我们怎么使用库函数,动静态库中提供了库函数的具体实现方法。
2 make和makefile
make 是方法,makefile是文件
创建makefile文件
写入makefile文件
效果:
从上面的操作中,我们可以看到,以前我们每次编译文件,都需要输入gcc等一系列命令。现在只需要使用make,就自动编译成功了。
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
接下来分步解析。
2.1 依赖关系
什么是依赖关系?
a:b -----> 叫做a依赖b,有了b的存在才会有a的存在。
在makefile中,gcc编译的时候有哪些依赖关系呢?
2.2 依赖方法
依赖关系解决的谁以来谁的问题
依赖方法解决的是怎样依赖的问题
红框的内容就是依赖方法
在makefile中依赖关系和依赖方法是必须写的,并且必须是一一对应的
2.3 伪目标
在makefile中同样是可以删除文件的。
执行情况
其中,.PHONY:clean
这句话中.PHONY就是伪目标,clean被.PHONY修饰的
- 被.PHONY修饰的对象就是一个伪目标。
- 伪目标总是被执行。
如何理解伪目标总是被执行这句话?
首先理解什么文件不是总被执行。
可以看到,在执行第一次make的时候,可以正常执行;执行第二次make的时候就不能了。
为什么呢?
在使用stat命令之后,可以看到一个文件的ACM时间,分别是图上所示。
我们使用ll命令显示的就是文件的内容修改时间
可以看到test.c文件的最后一次被修改时间比test的最后一次被修改时间早。
这就意味着:可执行文件如果再次执行,其实内容是没有改变的。
如果test.c文件在test文件之后又被修改过,那么Modify时间应该比test文件的时间晚,这样再次编译的时候才会有效果。而如果test.c的时间比test时间早,说明test.c文件没有被修改过。因此也就没有再次编译的必要。
这是编译器为了更高效的举动。
验证
从上面可以知道,make不是总被执行,那么。伪目标是总被执行的,怎么验证?
只要执行make clean
就一直会执行,不会出现阻碍。
而clean正是被.PHONY修饰的伪目标
执行:
发现给make加上伪目标之后,make也总是被执行。
**clean:**之后没有东西,说明依赖关系也可以为空
3 总结
- 编译链接分为:预处理、编译、汇编、链接四个部分。使用到的命令如下:
- .c文件预处理变成.i文件
- .i文件编译变成.s文件
- .s文件汇编变成.o文件
- .o文件链接变成可执行程序
其中,链接分为动态链接和静态链接。
静态链接将静态库中的.o程序拷贝到我们的程序,执行静态链接需要到系统默认的路径usr/lib中去找静态库,如果找不到就会报链接错误。
如果使用的是第三方库,需要用特定的方法链接到库。
- makefile中有:依赖关系,依赖方法和伪目标。
其中,依赖关系和依赖方法是一一对应的。
依赖关系也可以为空。
伪目标.PHONY表示总是执行
没有伪目标的文件通过判断文件的Modify时间来决定是否编译:
- 如果.c文件的Modify时间早于可执行文件 ---- 不执行
- 如果.c文件的Modify时间晚于可执行文件 ---- 执行