1.由源码到可执行程序的过程
源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序
-> 预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这几个工具再加上其他一些额外的会用到的可用工具,合起来叫编译工具链。gcc就是一个编译工具链。
2.预处理的意义
-> 预处理器帮编译器做一些编译前的杂事.
-> 编译器本身的主要目的是编译源代码,将C的源代码转化成.S的汇编代码。编译器聚焦核心功能后,就剥离出了一些非核心的功能到预处理器去了。
3.编程中常见的预处理
-> #include 头文件原地展开。
-> 注释,会把所以注释去掉。
-> #if #elif #endif #ifdef,选择需要的。
-> 宏定义,替换掉。
4.gcc中只预处理不编译的方法
gcc -E xx.c -o xx.i
可以实现只预处理不编译,一般情况下没必要只预处理不编译。
gcc xx.c -o xx
可以指定可执行程序的名称。
gcc xx.c -c -o xx.o
可以指定只编译不连接,也可以生成.o的目标文件。
preprocess.c源代码文件, 通过执行gcc -E preprocess.c -o preprocess.i (只预处理不编译)
得到preprocess.i.
#define pchar char *
typedef char * PCHAR
int main(void)
{
pchar p3,P4;
PCHAR p1, p2;
return 0;
}
typedef char * PCHAR
int main(void)
{
char * p3,P4;
char * p1, char * p2;
return 0;
}
总结:宏定义被预处理时的现象有:第一,宏定义语句本身不见了;第二,typedef重命名语言还在,说明它和宏定义是有本质区别的(说明typedef是由编译器来处理而不是预处理器处理的);
5.头文件包含
-> #include <>
和 #include" "
的区别:<>专门用来包含系统提供的头文件,""用来包含自己写的头文件;
->
<>的话C语言编译器只会到系统指定目录(编译器中配置的或者操作系统配置的寻找目录,譬如在ubuntu中是/usr/include目录来查找头文件。
->
""包含的头文件,编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后再到系统指定目录去寻找。
->
头文件包含的真实含义就是:在#include<xx.h>的那一行,将xx.h这个头文件的内容原地展开替换这一行#include语句。过程在预处理中进行。
6.注释
->
注释是给人看的,不是给编译器看的。
->
编译器既然不看注释,那么编译时最好没有注释的。实际上在预处理阶段,预处理器会拿掉程序中所有的注释语句,到了编译器编译阶段程序中其实已经没有注释了。
注意:
->
注释格式尽量统一,建议使用/**/。Linux内核中的注释几乎都使用这个。
->
边写代码边注释,更改代码的同时更改注释,保证注释与代码一致。
->
注释和代码同缩进,并且注释与上方代码之间要空出一行。
7.条件编译
有时候我们希望程序有多种配置,我们在源代码编写时写好了各种配置的代码,然后给个配置开关,在源代码级别去修改配置开关来让程序编译出不同的效果,条件编译中用的两种条件判定方法分别是#ifdef 和 #if。
int a = 0;
#ifdef NUM // 如果前面有定义NUM这个符号,成立
a = 111;
printf("#ifdef NUM.\n");
#else // 如果前面没有定义NUM这个符号,则执行下面的语句
a = 222;
printf("#elif.\n");
#endif
printf("a = %d.\n", a);
int a = 0;
#if (NUM == 0) // 如果前面有定义NUM这个符号,成立
a = 111;
printf("#ifdef NUM.\n");
#else // 如果前面没有定义NUM这个符号,则执行下面的语句
a = 222;
printf("#elif.\n");
#endif
区别:#ifdef XXX
判定条件成立与否时主要是看XXX这个符号在本语句之前有没有被定义,只要定义了这个符号就是成立的。
#if的格式是:#if (条件表达式),
它的判定标准是()中的表达式是否为true还是flase,跟C中的if语句有点像。