(程序员的自我修养)了解程序运行之前都干了些什么

面向工资编程 2017-12-15

前言

开篇先介绍两篇文章,一篇是大神bestsWifter《程序员的自我修养》读书总结

还有一篇我感觉也是非常好的文章,作者教会我们对待学习的态度为什么知乎Live,分答,得到都不是干货?-20171123

其实下面我要记录下的内容也都是<程序员的自我修养>这本书里面的内容,这里记录下来

正题

C 语言的经典,“Hello,World”程序几乎是每个程序员闭着眼睛都能写出来的,编译运行一气呵成。

#include <stdio.h>

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

在Linux下,当我们使用GCC来编译Hello World程序时,只须使用最简单的命令(假设源代码的文件名为hello.c)

$gcc hello.c
$./a.out
Hello World

事实上,上述过程可以分解为4个步骤,分别是预处理(Prepressing),编译(Compilation),汇编(Assembly)和链接(Linking)

  • GCC 编译过程分解图(程序员的自我修养)了解程序运行之前都干了些什么

预编译

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

或者

$cpp hello.c > hello.i

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

将所有的“#define”删除,并且展开所有的宏定义

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

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

删除所有的注释

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

保留所有的#pragma编译器指令,因为编译器需要使用他们

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

编译

编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。上面的编译过程相当于如下命令

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

汇编完成后生成一个.s 文件。对于C语言的代码来说,这个预编译和编译过程的程序是cc1,对于C++来说,有对应的程序叫cc1plus,对于Object-C 是cc1obj。实际上gcc这个命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译程序cc1,汇编器as,连接器Id

汇编

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

$as hello.s -o hello.o

或者

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

或者使用gcc命令从C源代码文件开始,经过预编译,编译,汇编直接输出目标文件(Object File):

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

链接

链接通常是一个让人比较费解的过程,为什么汇编器不直接输出可执行文件而是输出一个目标文件?链接过程到底包含了什么内容?为什么要链接?直接调用Id 运行Hello World 程序:

#ld -static /usr/lib/crt1.o /usr/lib/crti.o
/usr/lib/gcc/i468-linux-gnu/4.1.3/crtbeginT.o
-L/usr/lib/gcc/i468-linux-gun/4.1.3 
-L/usr/lib -L/lib hello.o --start-group-lgcc -lgcc_eh -1c --end-group 
/usr/lib/gcc/i468-linux-gun/4.1.3/crtend.o 
/usr/lib/crtn.o

如果把上面的路径都省略掉,那就是

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc -lgcc_eh -1c-end-group crtend.o crtn.o

可以看到,我们将一大堆文件链接起来才可以得到"a.out",既是最终的可执行文件。估计大家对crt1.o crti.o crtbeginT.o crtend.o crtn.o 这些文件是什么,有什么作用, -lgcc -lgcc_eh -lc 这些又是什么参数,为什么要将他们和hello.o 链接起来才可以得到可执行文件 ,有很多疑惑又或者不知道,那么就让我来告诉你们,其实我也不知道。。。 哈哈。。。所以才要学习。不过从<程序员的自我修养> 这本书里面大家可以找到答案。

程序员的自我修养 这本书非常值得拜读,之前只是大致的读了一些,现在忘的也差不多了,趁着这次从某书过来的一次机会再重新读一遍,并把一些重要的地方记录下来,方便加深记忆和理解。孔老夫子说的好,温故而知新,看一遍可能只是有个印象,两边是记住,三遍可能才能从中理解到真正的含义。当然不排除有些大神,看一遍就可以了。

共勉

相关推荐