程序员成长计划程序员@IT·互联网

“Hello,World”背后的故事

2016-08-10  本文已影响622人  卡巴拉的树

学习一门语言,经常都是从打印“Hello,World”开始的,打过招呼后,你便可以进入程序的新世界。

就拿经典的C语言举例,基本上每个程序员在上学时就可以闭着眼睛写下“Hello,World”,这也是检测开发环境是否能正常工作常用的小程序,就像有的人看能不能上网就输个百度试试(手动斜眼,程序员应该用谷歌).

//hello.c
#include <stdio.h>

int main()
{
    printf("Hello World\n");
    return 0;
}

我们使用gcc编译并运行该文件:

$ gcc hello.c -o hello
$ ./hello

输出结果:


helloworld

其实,输出一行字符并没有那么简单,gcc帮我们处理了很多步,如果你用Visual Studio,运行按钮更是连编译指令都不用敲了,IDE是简化了很多步骤,但是深入探索背后的步骤是每个程序员必备的素养,更何况很多成熟的大型项目都是需要自己构建(Build)。

上述过程可以分为4个步骤:

gcc编译过程

下面我们详述这一过程:

预处理##

预处理器cpphello.c及包含的头文件,这里就是stdio.h预编译成为一个hello.i的文件。
我们可以用以下命令只对hello.c进行预处理:

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

或者:

$ cpp hello.c > hello.i

你没看错,预处理器就是cpp,与C++扩展名.cpp没有关系,具体可以man cpp查看手册,其实gcc只是把预编译器,编译器,汇编器,链接器这一系列工具集成在一起,通过不同的参数去调用不同的部分或者全部调用

预处理做的工作:

经过预处理后,文件中所有的宏被展开,包含的文件也被插入,这时候就可以给编译器使用。

编译##

编译过程是整个程序构建的核心部分,包含了大量编译原理的知识,注明的参考书有龙书
编译过程可以分为以下几个部分,每个部分深究起来都很耗费功夫,有机会可以自己实现:

现在版本的gcc把预处理和编译两个步骤合二为一,使用一个叫cc1的程序完成这两个步骤,在我的计算机里位于“/usr/lib/gcc/i686-linux-gnu/4.8/cc1”
我们可以通过以下命令生成编译后的文件:

$ gcc -S hello.c -o hello.s

也可以直接使用cc1:

$ /usr/lib/gcc/i686-linux-gnu/4.8/cc1 hello.c

编译后生成汇编文件hello.s

汇编##

汇编器就是将汇编代码转变成机器可以执行的指令,每一条汇编语句几乎都对应一条机器指令。所以汇编器相对简单,只需要一一翻译就可以。
我们使用汇编器as完成如上工作:

$ gcc -c hello.s -o hello.o

或者

$ as hello.s - o hello.o

也可以直接从hello.c直接得到目标文件:

$ gcc -c hello.c -o hello.o

链接##

据说链接器的历史比编译器还长,像我们的“Hello,World”程序,生成的hello.o中包含了printf函数,头文件只包含了函数的申明,所以最后还需要链接到libc.a,其实需要链接的不仅仅是printf,我们用链接器ld链接以下这么多模块才能生成最终的可执行文件。

$ ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i686-linux-gnu/4.8/crtbeginT.o 
-L/usr/lib -L/lib hello.o --start-group -lgcc -lgcc_rh -lc --end-group 
/usr/lib/gcc/i686-linux-gnu/4.8/crtend.o /usr/lib/crtn.o

一个再复杂的软件也是如此,将源代码分别独立编译,再组装起来,这个过程就叫做链接,链接的主要目的,一个是将模块间依赖的函数调用打通,还有就是模块间共通的变量打通。

链接器所做的工作主要就是“调整地址”,写汇编代码时,有这么一句jmp foo,其实链接器就帮我们把foo翻译成运行时的地址。

链接的主要过程:

举个例子,可以很清楚的解释这个过程,我们在main.c调用了另外一个文件func.c中的函数test(),那么当我们在main.c中每使用一次test()都必须知道test()的地址,但文件都是单独编译的,所以我们在main.c中的做法是暂时搁置test()的地址,当链接的时候,链接器会根据test符号,自动填入test()的地址,如果func.c重新编译了,test()地址会变化,但是编译时,没有改变的main.c并不会编译了,只是在链接时,会链接新的test()的地址。这个修正的过程也叫作重定位

链接还分为静态链接动态链接,这个以后会专门说。

如果觉得还不错,请点个赞吧~

上一篇 下一篇

猜你喜欢

热点阅读