深入理解计算机系统之三 -- C程序编码
假设有一个hello.c
程序,代码如下:
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
我们在unix
系统中编译
gcc -o hello hello.c
编译后执行./hello
,就能看到结果了。
当我们在系统上执行hello
程序时,系统发生了什么?
hello
程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂。然而,为了在系统上运行hello.c
程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令。然后这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件。
在Unix
系统上,从源文件到目标文件的转化是由编译器驱动程序完成的:
在这里, GCC编译器驱动程序读取源程序文件hello.c
,并把它翻译成一个可执行目标文件hello
。这个翻译过程可分为四个阶段完成,如图所示。执行这四个阶段的程序(预处理器、编译器 、汇编器和链接器)一起构成了编译系统(compilation system)。
预处理阶段:预处理器(CPP)根据以字符#开头的命令,修改原始的C程序。比如hello.c
中第1行的#include<stdio.h>
命令告 诉预处理器读取系统头文件stdio.h
的内容,并把它直接插入 程序文本中。结果就得到了另一个C程序,通常是以.i
作为文件扩展名。
编译阶段:编译器(ccl) 将文本文件hello.i
翻译成文本文件hello.s
,它包含一个汇编语言程序。该程序包含函数main
的定义。
在命令行上使用“-S”选项,就能看到C语言编译器产生的汇编代码,这会使GCC运行编译器,产生一个汇编文件hello.s
,但是不做其他进一步的工作。
gcc -Og -S hello.c
汇编阶段:接下来,汇编器(as)将hello.s
翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序 (relocatable object program) 的格式,并将结果保存在目标文件hello.o
中。hello.o
文件是一个二进制文件,它包含的 17个字节是函数main
的指令编码。如果我们在文本编辑器中打开hello.o
文件,将看到一堆乱码。
如果我们使用 “-C” 命令行选项,GCC会编译并汇编该代码:这就会产生目标代码文件hello.o
, 它是二进制格式的,所以无法直接査看。
gcc -Og -c hello.c
在Linux
系统中,带 ‘-d’ 命令行标志的程序OBJDUMP
(表示 “object dump”)可以充当这个角色:
objdump -d hello.o
链接阶段: 请注意,hello
程序调用了printf
函数,它是每个C编译器都提供的标准C库中的一个函数。printf
函数存在于一个名为printf.o
的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.o
程序中。链接器(Id)就负责处理这种合并。结果就得到hello
文件,它是一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。
gcc -o hello hello.c