编译那些事儿1-预编译

最近要深入了解一下计算机的内部结构了,准备看一下《深入理解计算机系统》这本书,这就当是自己的学习笔记吧。

这里都是以Unix或类Unix操作系统为基础做的工作。

编译包括什么

编译一般分为四个阶段:预处理、编译、汇编、链接。这里就认真了解一下预编译的事儿。

生成预处理文件

比如我们有一个c语言编写的”hello.c”文件如下:

1
2
3
4
5
6
#include <stdio.h>

void main()
{
printf("Hello world\n");
}
  • gcc只做预处理不进行编译和链接操作的方法,预处理产生的文件为*.i文件:
1
gcc -E hello.c -o hello.i
  • gcc编译阶段产生汇编文件:
1
gcc -S hello.i -o hello.s
  • 汇编阶段产生.o文件:
1
gcc -c hello.s -o hello.o
  • 编译成可执行文件的方法:
    1
    gcc hello.o -o hello

这里我们重点关注一下预处理阶段编译器做了什么事情,预处理有什么用?预处理后的结果是什么?

我对预处理的疑问

为什么要有预处理?

首先程序编译后是给机器运行的,预处理就是优化我们人类写的代码结构,比如将使用到以 #define 宏定义的内容的部分替换成真实的内容,这样程序在运行的时候就不需要再进行响应的替换操作了。

另外如果我们程序中有 #if 定义的条件语句,预处理也会尽量精简,比如确定条件成立的话就不在执行 #else后面的语句,也就是提起做了条件语句的判断,这样也会节省我们程序执行时所耗费的时间。

当然预处理还有很多其他的处理,总体感觉上就是:把人写的代码做优化,方便后续给机器执行,提高效率。

预处理后的结果是什么?

产生了一个*.i文件,这个文件是预处理后的结果,里面包括优化后的代码和一些头文件的内容,具体就是讲头文件的内容展开插入到了当前的.i文件中,包括一些变量、结构体、函数的声明和定义。

比如这里产生的hello.i文件中就包含了int、char、short等数据类型的定义,如下:

1
2
3
4
5
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
……下面还有很多定义……

其实就可以理解为预处理后的文件就是原文本文件包含头文件的扩展和代码的优化版本(听起来可能有点乱,我需要捋一捋)。

预处理了哪些东西?

头文件

对于文本文件中包含的头文件,预处理会将头文件的声明,将其一锅端地都放到要生成的hello.i文件内,就像上面的hello.i的内容一样,把stdio.h中声明的变量、结构体、函数、联合体等都放到了要生成的hello.i文件内。

条件编译

包括#ifdef和#if。

预处理器会自动判断#ifdef或#if后面的条件语句是否成立,如果可以判断出成立那么#else后面的内容就不会出现在hello.i文件内了,这就是预处理做出的代码优化部分(我姑且称之为优化吧)。

注释

注释统统去掉,因为注释是给人看的,对于机器来说没有任何意义,所以去掉去掉!

宏定义

对于使用#define定义的宏定义,预处理会将宏定义使用到的地方全部替换成宏定义的具体内容。

这里需要留意一下冲定义typedef,typedef定义的内容不会被预处理器处理,保持原样,在编译阶段才会做替换。