编译器的主要工作;
-
源程序分析。语法分析、词法分析、语义分析、类型检查等等,这一阶段的目标是主要是检查代码有没有错误,就像我们常见的error和warning就是这个阶段确定的。
-
预处理。预处理器会展开目标模块导入的头文件和替换宏定义,预处理后生成 *.i文件。
-
编译。编译器将*.i文件编译成 ASCII 汇编语言文件*.s。
-
汇编。汇编器将 *.s文件汇编成一个可重定位的二进制目标文件*.o,Mac OS 和 iOS 中称为 Mach-O文件。
-
链接。链接分为动态链接和静态链接,链接器将所有的目标文件和系统目标文件组合起来,生成能在机器上运行的可执行文件。iOS 中为*.ipa,Windows 中为*.exe,Android 中为*.apk等
一:预处理
预处理主要进行宏定义分析、文件包含、根据条件决定是否编译等过程;预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并 对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。预处理过程中主要处理的指令有:
指令 用途 # 空指令,无任何效果 #include 包含一个源代码文件 #define 定义宏 #undef 取消已定义的宏 #if 如果给定条件为真,则编译下面代码 #ifdef 如果宏已经定义,则编译下面代码 #ifndef 如果宏没有定义,则编译下面代码 #elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码,其实就是else if的简写 #endif 结束一个#if……#else条件编译块 #error 停止编译并显示错误信息
(1)#include 头文件: 头文件包含即 找到包含的头文件,并把内容展开在要生成的目标文件*.i中;#include有两种包含方式:
#include<a.h> 在编译器自带或外部引用的库中寻找头文件 #include"a.h" 在被编译的源代码中寻找头文件
(2)#define 宏定义;
1:注意
(3)#和##符的使用:
#符:#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号;
举个例子:
#define WARN_IF(EXP) do{ if (EXP) fprintf(stderr, "Warning: " #EXP "/n"); } while(0)
WARN_IF (divider == 0); 被替换为 do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "/n"); } while(0);
##符:连接符,常见使用场景:
#define NUM(a,b,c) a##b##c #define STR(a,b,c) a##b##c int main() { printf("%d ",NUM(1,2,3)); printf("%s ",STR("aa","bb","cc")); return 0; } //最后程序的输出为: 123 aabbcc
通常连接符还可以用于复杂结构体定义,避免代码冗余:比如一个包含函数指针的结构体:
struct command { char * name; void (*function) (void); };
通过定义宏 #define COMMAND(NAME) { NAME, NAME ## _command };并使用定义好的宏来初始化数组
struct command commands[] = { COMMAND(quit), COMMAND(help), ... }
二:条件编译:
(1)较为常见:#if指令检测跟在制造另关键字后的常量表达式。如果表达式为真,则编译后面的代码,知道出现#else、#elif或#endif为止;否则就不编译。#endif用于终止#if预处理指令。#else指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码;
(2) #ifndef;主要用于防止重复包含。我们一般在.h头文件前面加上这么一段:
//头文件防止重复包含 //funcA.h #ifndef FUNCA_H #define FUNCA_H //头文件内容 #end if
三:特殊符号
预编译程序可以识别一些特殊的符号。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
注意,是双下划线,而不是单下划线 。
__FILE__ 包含当前程序文件名的字符串
__LINE__ 表示当前行号的整数
__DATE__ 包含当前日期的字符串
__STDC__ 如果编译器遵循ANSI C标准,它就是个非零值
__TIME__ 包含当前时间的字符串