从源代码到可执行程序:四个步骤与详解

Posted fensi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源代码到可执行程序:四个步骤与详解相关的知识,希望对你有一定的参考价值。

"hello world"可以说是所有程序员闭着眼睛都能写出来的代码:

#include <stdio.h>
int main()
{
    printf("hello world
");
    return 0;  
}

编译运行一气呵成。而每当有人问起:从源码到可执行程序有哪些步骤,大多数程序员面对这个问题也能脱口而出:预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)

不过很多人不了解其中都做了哪些处理,今天就带大家来好好聊一聊。

预编译

处理的第一步,是将源码文件.c和头文件.h编译成一个.i文件。

预编译过程主要是做了以下一些工作:

将所有的#define删除,展开所有的宏定义

处理条件编译指令,比如#ifdef、#ifndef、#if、#endif等等

处理#include指令,将头文件插入到该指令的位置。这个过程是递归的,也就是说被包含的头文件还可能包含其他头文件

删除所有注释

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

保留所有的#pragma编译器指令,因为编译器要使用它们。#pragma的作用是设定编译器的状态或者是指示编译器完成一些特定的动作

编译

编译过程就是将预处理完的文件进行词法分析语法分析语义分析优化后产生相应的汇编代码文件

词法分析

词法分析主要使用词法分析器(也叫扫描器),将源代码的字符序列分割成一系列的符号(Token)。比如如下一段程序:

int array = (index + 4) * 2;

经过扫描以后,产生11个记号:

int         关键字

array      标识符

=         赋值操作符

(        左小括号

index      标识符

+        加号

4        数字

)        右小括号

*        乘号

2        数字

;        语句结束

语法分析产生的记号一般可以分为:关键字,标识符,字面量(包括数字和字符串等)和特殊符号(加号减号等)。在识别记号的同时,扫描器也完成其他工作,比如讲标识符存放到符号表,讲数字字符串常量存放到文字表,以备后面的步骤使用。

语法分析

接下来语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用了上下文无关语法的分析手段。

语义分析

这个阶段由语义分析器来完成。语法分析仅仅完成了对表达式的语法层面的分析,他并不了解这个语句是不是真的有意义。比如两个指针相乘是没有意义的,但是在语法上是合法的。

编译器可以分析的语义是静态语义,即在编译器就可以确定的语义;与之对应的是动态语义,即在运行期才可以确定的语义。

静态语义通常包括声明和类型的匹配,类型的转换。比如一个浮点型表达式赋值给整形表达式的时候,语义分析会完成浮点型到整形的转换。动态语义使之运行期出现的语义相关问题,比如除数是0的时候会报运行期语义错误。

源代码优化

现代编译器有很多层的优化,往往在源代码级别会有一个优化过程。源代码优化器会在源码级别进行优化,比如一行代码:

array[index] = (index + 4) * (2 + 6);

在这行代码中,(2+6)这个表达式就可以被优化掉,因为他的值在编译器就可以确定。

在进行了语法分析和语义分析阶段的工作之后,有的编译程序将源程序变成一种内部表示形式,这种内部表示形式叫做中间语言或中间表示或中间代码。所谓“中间代码”是一种结构简单、含义明确的记号系统,这种记号系统复杂性介于源程序语言和机器语言之间,容易将它翻译成目标代码。

中间代码使得编译器可以分为前端和后端,前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对于一些跨平台的编程语言,他们可以针对不同平台使用同一个前端和针对不同平台的数个后端。

目标代码生成与优化

源码级优化器产生中间代码标志着下面的过程都属于编辑器的后端。编译器后端主要包括代码生成器和目标代码优化器。代码生成器将中间代码转换成目标机器代码,然后目标代码优化器进行代码优化,比如选择合适的寻址方式、食用为宜来代替乘法运算,删除多余的指令。

 

经过扫描、词法分析、语法分析、语义分析、源代码优化、代码生成和目标代码优化,源代码终于被编译成了目标代码。但是现在还有一个问题:目标代码中有的变量定义在其他模块,我们该怎么办?

事实上,定义在其他模块的变量和函数在最终运行时的绝对地址都要在链接的时候才能确定。所以现代编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。

链接

暂时挖个坑

以上是关于从源代码到可执行程序:四个步骤与详解的主要内容,如果未能解决你的问题,请参考以下文章

从源代码到可执行文件

从源代码到可执行文件

源代码到可执行程序的过程详解:预编译编译汇编链接

C/C++ - 从代码到可执行程序的过程

从程序到进程

linux下的静态库与动态库详解