C程序的生成需要经过预处理(预编译)、编译、汇编和链接四个步骤才可以执行。
参考:程序的编译流程
一、预处理
其中在预处理步骤,预处理器会执行宏替换、条件编译以及包含指定的文件。
1. 宏替换
一般的宏定义形式如下:
#define 名字 替换文本
对于带有参数的宏替换,一定需要记得带括号,因为宏替换是按原样替换,不对参数打括号会在一些情况产生歧义:
#define max(A,B) ((A)>(B) ? (A) : (B))
x = max(p+q,p-q)
如果不带括号,那么宏替换出来就是这样,很显然由于运算符运算顺序的不同会导致不同的结果。
x = (p + q > p - q ? p + q : p - q);
为何要是用宏定义
宏的展开类似于内联函数的展开,他们不需要数据的进出栈,因此也可以节省时间,但由于会大量的代替占据空间,因此有点类似用空间换时间的感觉。其次就是宏可以无视数据的类型,在C中达到泛型的结果,类似C++中的重载。
2. 条件编译
打开头文件,我们会看到头文件的都有一个固定的格式:
#ifndef _MY_H_
#define _MY_H_
...
#endif
其中的 #ifndef 表示开始,如果在导入头文件的文件中之前没有导入该头文件就定义这个宏,否则就跳过下述的定义直接结束。这个宏的作用就是单纯的定义一个标记,告知其他的头文件,后续的内容已经被预处理过,避免头文件被重复加载,这样其他的引用该头文件的头文件进行#include时,一看到要定义的_MY_H_已经被定义过,就不会再定义了。所以 #ifndef 的这个宏定义名字没有固定的规格,只要保证这个宏定义只在该头文件中被定义过即可,但为了阅读方便,我们一般把文件前后缀加上_
,.
也改为_
来命名。
3. 包含文件
以"#"开头的命令行就是预处理器处理的对象。其中两个最常用的预处理指令是:#include指令(用于在编译期间把制定的文件内容包含进当前文件中)和 #define指令(用任意字符序列代替一个标记)。一般 #inlude 文件后面跟着头文件的名字,但后面跟着的形式却有所不同,有两种形式:
#include <stdio.h>
#include "my.h"
<>
表示引用的是编译器的类库路径里面的头文件
" "
表示引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件,例如想引用下面连个文件路径中的stack.h文件就需要不同的声明
#include"stack.h"
#include"stack/stack.h"
二、编译
三、汇编
四、链接
链接阶段的目的就是将汇编阶段生成的所有目标文件及依赖的库文件链接到一起,生成最终的可执行文件。
1. 分类
链接分为静态链接和动态链接。
-
静态链接:在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接
-
动态链接:动态链接出现的原因就是为了解决静态链接中提到的两个问题,一方面是空间浪费,另外一方面是更新困难。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
2. 不同系统下的文件
windows下.lib
是静态链接库的库文件,就是 link 的时候把里面需要的东西抽取出来安排到你的exe文件中,以后运行你的exe的时候不再需要.lib
。.dll
是动态链接库的库文件,就是exe运行的时候依赖于dll里面提供的功能,没有这个dll,你的exe无法运行。 .obj
里存的是编译后的代码跟数据,并且有名称,所以在连接时有时会出现未解决的外部符号的问题。当连成exe后便不存在名称的概念了,只有地址。.lib
就是一堆.obj
的组合。
理论上可以连接 .obj
文件来引用其他工程(可以认为一个 .obj
文件等价于编译生成它的cpp文件,可以引用 .obj
来替换cpp,也可以添加cpp来替换 .obj
),但实际中通常用.lib
来实现工程间相互引用。编译器会默认链接一些常用的库,其它的需要你自己指定。
linux 下.o
是目标文件,相当于windows中的.obj
文件 ,.so
为共享库,是shared object,用于动态连接的,相当于windows下的.dll
。静态库为.a
,是好多个.o
合在一起,用于静态连接。 用gcc生成可执行代码时,使用-l
参数指定要加入的库函数(.a
)。也可以用ld
命令的-l
和-L
参数。在Makefile中定义Libraries也同理
参考:windows下obj、lib、dll、exe和Linux的.a、.so和.o文件
一般在使用动态库的时候都直接把.dll
放在.exe
相同的文件夹下,对应的还要有一个 .dll
对应的.a
文件,放在 Lib 文件夹中,就类似于.dll
是一个声明和手册,实际的执行还在对应的.a
中,而如果使用静态链接,就只有一个.a
了。
参考:dll与exe的区别
3. glfw中的静态与动态库使用
在学习opengl的时候发现使用的链接库的后缀名是.a,而不是.lib。这是因为我使用的是MinGW,它的编译器是gcc,gcc是一个跨平台的编译器,使用.a
文件作为静态链接库文件。在windows平台下,.a
文件和 .lib
文件都使用了COFF格式,所以在一般情况下可以直接使用,除非在编译他们的时候使用了编译器特有的机制。对于一般的文件可以使用MinGW自带的程序reimp 将.lib
转换为.a
文件。
在下载的 glfw 预编译版本中,有三个文件:
其中 glfw3.dll 和对应的 libglfw3dll.a是动态链接用的,而 libglfw3.a 是静态链接用的。如果使用动态链接,则我们需要将对应的.dll
文件放在.exe
相同目录下,对应的.a
放在Lib文件夹下
在makefile时,使用 -l
指定静态库文件进行连接,注意链接的名字写的是静态库的名字,同时Makefile提供了简写,-l1 表示 lib1.a缩写