当你执行cl -o hello.exe hello.c或者 cc -o hello hello.c时,到底发生了一些什么呢?其实cl或是cc实际上调用了另外两个程序。首先调用预处理程序(UNIX下是cpp程序,在Windows下不太清楚),预处理源文件。然后自己编译之,最后调用link.exe或是ld程序来完成连接成生可执行代码。这3个操作是靠管道连在一起的。要是不知道什么是管道就姑且认为,是分3步,用临时文件连在一起的。C语言的预处理机制不属于语言的一部分,但是它的作用非常重要。
在C语言中,以#开头的行都是预处理命令。最常用的预处理命令就是#include,#define。常用的还有#ifdef #ifndef #pragma。 记住一点,大多数预处理命令基本只是执行一个文本替换的过程。其他的预处理指令我们以后再讲,这里只讲讲#inclue。
#include 有两种形式,第一种是#include <stdio.h> , 第二种是#include "my.h"。 这两种形式的区别在于找头文件的默认路径不一样。 对于#include <stdio.h> 会 去INCLUDE环境变量指定的目录去找stdio.h这个文件。而#include "my.h"会在当前目录去找my.h,这个“当前目录”是指使用这条预处理命令的源文件所造的目录。比如,我有一个文件hello.c在C:/Cpp/ 目立下,即C:/Cpp/hello.c ,那么编译的时候预处理程序会在C:/Cpp/下找有没有叫my.h的头文件。按照上次讲的如果你写#include "../my.h"就是在C:/下找my.h这个文件。对于第一种情况可以不改INCLUDE环境变量而针对单个源文件来扩展搜寻的目录。 就是在编译的时候使用 -I参数。
比如我安装了一个附加库,比如MPICH2的mpi实现, MPICH2安装在D:/mpich2下,那么提供的头文件一般是在D:/mpich2/include。如果我要在程序中包含mpi.h,只需要写了#include <mpi.h> 。 但是在编译的时候比如加上一个选项 -ID:/mpich2/include。 否则编译的时候编译器就会抱怨无法打开mpi.h。
当然你也可以写成绝对路径 #include <d:/mpich2/include/mpi.h>。这样也可以编译通过,但是这样带来的问题是,比如你的同学拷贝你的程序在他的计算机上编译,由于他把mpich2安装在e:/software/mpich2下了,于是编译器又开始抱怨无法打开mpi.h。同理,写#include "my.h"的时候也不要写绝对路径。可能你会说,头文件不和源文件放在一起吗?还需要写相对路径吗?当你的程序由上百上千个文件组成时,可能就需要将源码组成一颗源码树。看看linux内核的源码就明白了。比如你的程序在D:/pro/hello/目录下,你把源文件全放在D:/pro/hello/src下,头文件全放在D:/pro/hello/include下, 那么include一个头文件最好写成 #include "../my.c" 而不要写成#include "D:/pro/hello/include/my.h"。 写成后者将会使的你的源代码树只能放在D:/pro下才能正确编译,而写成前者,即使你的源码拷贝到一台Linux机器上也能正确编译。
(注,如果你细心你就会发现 #include "../my.c"中用的是/,而不是/。这才保证了在windows和unix下都能正确识别)
你会发现预处理对于#include 只是把stdio.h的内容直接插入到了源文件里面。在里面有一行
int __cdecl printf( const char * _Format, ...);这句话保证了你能正确的调用printf("Hellp world!/n");
等等! 那printf的代码在那里?难道#include 不是给我们的程序增加了printf子程序的代码吗?答案是printf实际的代码是在后面连接的时候才加入你的代码的。
#include 还有很多要注意的地方。这牵涉到头文件和源文件的关系,externl linkage和 internl lingage的区别等别的东西。这里只是强调,#include 某个头文件仅仅是讲这个头文件原样插入到了写#include 指令的位置。没有别的其他功能了。
下一篇讲连接link的问题。