深入理解计算机系统之三 -- C程序编码

臭大佬 2021-03-07 21:59:05 515
linux 
简介 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