"hello world"程序的背后

发布时间 2023-08-05 17:34:41作者: 桂洛克船长

​ 今天浅浅分享一下一个hello程序的背后发生了什么

​ 首先创建一个hello.c程序

#include <stdio.h>
 
int main()
{
 printf("Hello World\n");
 return 0; 
} 

​ 其实上述过程可以分解为4个步骤,分别是预处理、编译、汇编、链接,如图所示

image-20230805165606337

预处理

​ 首先是源代码文件hello.c和相关的头文件,如stdio.h等被预编译器cpp预编译成一个.i文件。对于C++程序来说,它的源代码文件的扩展名可能是cpp或.cxx,头文件的扩展名可能是.hpp,而预编译后的文件扩展名是.ii.第一步预编译的过程相当于如下命令(-E 表示只进行预编译):

$gcc -E hello.c -o hello.i

image-20230805171912851

预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include"、“#define”等,主要处理规则如下:

1、将所有的“#define" 删除,并且展开所有的宏定义。

2、处理所有条件预编译指令,比如“#if”、 “#ifdef"、 “#elif"、 "#else"、 “#endif"。

3、处理“#include"预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。

4、删除所有的注释“//”和“/* */”。

5、添加行号和文件名标识,比如#2“hello.c"2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。

6、保留所有的#pragma编译器指令,因为编译器须要使用它们。

经过预编译后的.文件不包含任何宏定义,因为所有的宏已经被展开, 并且包含的文件也已经插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。

编译

​ 编译过程就是把预处理完的文件进行一系列词法分析、 语法分析、语义分析及优化后生产相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。简单介绍编译的具体几个步骤,这涉及编译原理等一些内容,仅仅是介绍而己。上面的编译过程相当于如下命令:

$gcc -S hello.i -0 hello.s

image-20230805172007327

现在版本的GCC把预编译和编译两个步骤合并成一个步骤,使用一个叫做ccl的程序来完成这两个步骤。这个程序位于“/usr/ib/gcc/i486-linux-gnu/4.1/"。

都可以得到汇编输出文件hello.s。 对于C语言的代码来说,这个预编译和编译的程序是ccl,对于C++来说,有对应的程序叫做cclplus; Objective-C 是cclobj: fortran 是f771; Java 是jc1.所以实际上gcc这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用编译编译程序cc1、汇编器as、链接器ld。

汇编

​ 汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应-条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表-一-翻译就可以了,“汇编”这个名字也来源于此。上面的汇编过程 我们可以调用汇编器as来完成:

$as hello.s -o hello.o

或者:

$gcc -C he1lo.s -o hello.o

image-20230805172041809

链接

​ 链接通常是一个让人比较费解的过程, 为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?链接过程到底包含了什么内容?为什么要链接?这恐怕是很多读者心中的疑感。正是因为这些疑感总是挥之不去,所以我们特意用这章的篇幅来分析链接, 具体地说分析静态链接的章节。下面让我们来看看怎么样调用Id才可以产生一个能够 正常运行的HelloWorld程序:

$1d -static /uar/1ib/ert1.o /uer/11b/crt1.o

/u6r/11b/gcc/1486-11nux-gu/4.1.3/ertbegint.o

-L/u8r/11b/gcc/1486-11nux-gou/4.1.3 -L/u8x/11b -L/l1b hello.o--8tart-group-1gcc -1gcc _eb -1c --end-group /uar/1b/gcc/1486-11mux- gou/4.1.3/ertend.o/u8r/11b/crtn.o

如果把所有的路径都省略掉,那么上面的命令就是:

d -static crt1.o crti.o crtbegint.o hello.o -start-group -1gcc -lgcc_ eh -1c-end-group crtend.o crtn.o

可以看到,我们需要将一大堆文件 链接起来才可以得到“a.out",即最终的可执行文件。看了这行复杂的命令,可能很多读者的疑感更多了,crtl.o. cri.o、crtbeginT.o crtend.o、crtn.o这些文件是什么?它们做什么用的? -lgcc -lgcc _eh -lc这些都是什么参数?为什么要使用它们?为什么要将它们和hello.o链接起来才可以得到可执行文件?等等。

这些问题它们看似简单,其实涉及了编译、链接和库,甚至是操作系统的一 些很底层的内容。

这就是一个hello程序背后的内容了,其实后面还有更加细节的语法分析、词义分析、有限状态机等等知识,有兴趣的读者可以自行百度,这里不再赘述。