由我们写好的源代码到CPU可以认识并执行的二进制中间发生了很多事,我们来深入探讨一下C语言的预处理。
1.从源代码到可执行程序的过程
(1)源代码.c文件先经过预处理器,生成一个中间文件.i文件
(2).i文件经过编译生成汇编.s文件
(3).s的汇编文件经过汇编器生成.o的目标文件
(4).o的目标文件经过链接器生成.elf可执行程序
每一步都有其相应的工具,预处理有预处理器,编译有编译器,链接有链接器,这些工具合在一起叫做编译工具链。在windos上的编译器,这些都被所谓的编译器屏蔽了。只留出一个编译的功能,其实叫编译器是不准确的。在Linux中,gcc就是一个编译工具链。gcc包含预处理器,编译器,汇编器,链接器等。
2.gcc中只预处理不编译的方法
gcc xx.c -o 由源码到可执行程序
gcc xx.c -c -o 只编译不连接,生产.o的目标文件
gcc -E xx.c -o xx.i 只预处理产生的中间文件
看只预处理的中间文件有利于我们调试和研究其预处理过程,还记得我们之前说过的#define和typedef的细节差异吗?现在我们就来分析一下,并且看看预处理是个什么过程。
#include
#define pchar char*
void main(void)
{
pchar p1,p2;
}
//进行预处理操作,gcc -E xx.c -o xx.i 产生中间文件
#include
void main(void)
{
char* p1,p2;
}
分析:
(1)经过预处理后,原来的#define 代码不见了,直接在其所在位置进行替换
(2)#define的替换是简单的替换,就是把所有的pchar 用char*替换
(3)我们看p1,p2的类型。p1shi char*类型的,而p2只是char类型的
再来看typedef的使用
#include
typedef char* pchar
void main(void)
{
pchar p1,p2;
}
//预处理后
typedef char * pchar;
void main(void )
{
pchar p1,p2;
}
我们发现没有变化。也就是说typedef重定义的类型在预处理阶段是不产生变化的,而是在编译阶段发挥作用的。在编译阶段就把pchar当作普通的char*使用。
3.头文件在预处理阶段发生什么变化?
(1)有两种包含方式 第一种:#include<> 第二种:#include””
第一种是用来包含系统提供的头文件的
第二种是用来包含程序员自己写的头文件的
(2)两种包含的区别
第一种:#include< > 的方式,C语言编译器只会到系统指定的目录(编译器配置的目录)去寻找这个头文件,如果找不到就出错。但是编译器允许使用 - i命令添加更多的目录到该系统目录。(不会到当前目录去找)
第二种方式:#include”“的方式,编译器先当前目录下去寻找,如果没有找到则到系统指定的目录去找。这种方式也可以包含系统文件。
总结:(1)虽然可以使用“”来包含系统头文件,但是一般来说,遵从一定的规则。系统文件用<>,自己写的用“”,如果自己写的一大群头文件,也使用<>,把该目录添加到编译器指定的目录去即可。
(2)(头文件包含的实质)在1.c中包含1.h,就是把1.h中的代码原封不动的写到1.c中#include<>语句的原地方去。
—-1.c文件—-
# nclude"1.h"
main()
{
c = a + b;
printf("c = %d\n",c);
}
----1.h文件----
int a = 1;
int b = 2;
int c = 0;
预处理的命令:gcc -E 1.c -o 1.i
结果
# 1 "1.c"
# 1 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "" 2
# 1 "1.c"
# 1 "1.h" 1
int a = 1;
int b = 2;
int c = 0;
# 2 "baohan.c" 2
main()
{
c = a + b;
printf("c = %d\n",c);
}
分析:
(1)前面多了很多我们不认识的东西,不用管它,那是给编译器看的。
(2)在1.c 的#include”1.h”这句代码处,把1.h中的代码全部拷贝到了这里。
(3)需要注意的是,我们写的代码进过预处理后产生了一个.i文件,所有的改动都是在.i文件中。原来的.c文件补货被改动的。
4.注释在预处理阶段的变化
两种条件编译#iddef 和#if( )
#include
//声明函数
void test(void);
void main()
{
//定义变量
int a = 1;
int b = 2;
}
经过预处理后
#include
void test(void);
void main()
{
int a = 1;
int b = 2;
}
我们发现所有的注释都被删除了。
总结:
(1)注释是给人看的,编译器不用看。
(2)在预处理阶段,预处理器会全部删掉注释。也就是说在编译的时候,编译器根本不知道什么是注释。
(3)预处理器改的是预处理后的一个中间文件.i,而不是源代码.c
5.条件编译在预处理阶段发生了什么?
#include
#define test 3 //宏定义 test 为3
void main(void)
{
#ifdef test 3 //判断 test是不是被宏定义为3,是
printf(" test 3 !\n");
#else //不是
printf("else !\n");
#endif
}
预处理后
预处理后被包含进来的stdio.h没有写出来,太长了。
void main(void)
{
printf(" test 3 !\n");
}
分析:在预处理阶段,条件编译,会判断哪个条件成立,把条件成立的那个语句留下,不成的条件的那个被舍去。(在.i文件中,不是.c文件)
再来看第二种条件编译 #if()
#include
#define ok 1
main()
{
#if(ok)
printf("ok\n");
#else
printf("not ok\n");
#endif
}
//预处理后:
main()
{
printf("ok\n");
}
总结:
第一种#ifdef 是判断是否该符号被宏定义,在舍弃条件未成立的那一部分
第二种#if() 和if()一样。括号中是一个逻辑真假值。