由我们写好的源代码到CPU可以认识并执行的二进制中间经过很多步骤。
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<stdio.h> #define pchar char* void main(void) { pchar p1,p2; } //进行预处理操作,gcc -E xx.c -o xx.i 产生中间文件 #include<stdio.h> void main(void) { char* p1,p2; }
分析:
(1)经过预处理后,原来的#define 代码不见了,直接在其所在位置进行替换
(2)#define的替换是简单的替换,就是把所有的pchar 用char*替换
(3)我们看p1,p2的类型。p1shi char*类型的,而p2只是char类型的
再来看typedef的使用
#include<stdio.h> 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 ",c); }
----1.h文件----
int a = 1; int b = 2; int c = 0;
预处理的命令:gcc -E 1.c -o 1.i
结果
# 1 "1.c" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 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 ",c); }
分析:
(1)前面多了很多我们不认识的东西,不用管它,那是给编译器看的。
(2)在1.c 的#include”1.h”这句代码处,把1.h中的代码全部拷贝到了这里。
(3)需要注意的是,我们写的代码进过预处理后产生了一个.i文件,所有的改动都是在.i文件中。原来的.c文件补货被改动的。
4.注释在预处理阶段的变化
两种条件编译#iddef 和#if( )
#include<stdio.h> //声明函数 void test(void); void main() { //定义变量 int a = 1; int b = 2; }
经过预处理后
#include<stdio.h> void test(void); void main() { int a = 1; int b = 2; }
我们发现所有的注释都被删除了。
总结:
(1)注释是给人看的,编译器不用看。
(2)在预处理阶段,预处理器会全部删掉注释。也就是说在编译的时候,编译器根本不知道什么是注释。
(3)预处理器改的是预处理后的一个中间文件.i,而不是源代码.c
5.条件编译在预处理阶段发生了什么?
#include<stdio.h> #define test 3 //宏定义 test 为3 void main(void) { #ifdef test 3 //判断 test是不是被宏定义为3,是 printf(" test 3 ! "); #else //不是 printf("else ! "); #endif }
预处理后
预处理后被包含进来的stdio.h没有写出来,太长了。
void main(void) { printf(" test 3 ! "); }
分析:在预处理阶段,条件编译,会判断哪个条件成立,把条件成立的那个语句留下,不成的条件的那个被舍去。(在.i文件中,不是.c文件)
再来看第二种条件编译 #if()
#include<stdio.h> #define ok 1 main() { #if(ok) printf("ok "); #else printf("not ok "); #endif }
//预处理后: main() { printf("ok
"); }
总结:
第一种#ifdef 是判断是否该符号被宏定义,在舍弃条件未成立的那一部分
第二种#if() 和if()一样。括号中是一个逻辑真假值。