=====> COMPILATION PROCESS <======
|
|----> Input is Source file(.c)
|
V
+=================+
| |
| C Preprocessor |
| |
+=================+
|
| ---> Pure C file ( comd:cc -E <file.name> )
|
V
+=================+
| |
| Lexical Analyzer|
| |
+-----------------+
| |
| Syntax Analyzer |
| |
+-----------------+
| |
| Semantic Analyze|
| |
+-----------------+
| |
| Pre Optimization|
| |
+-----------------+
| |
| Code generation |
| |
+-----------------+
| |
| Post Optimize |
| |
+=================+
|
|---> Assembly code (comd: cc -S <file.name> )
|
V
+=================+
| |
| Assembler |
| |
+=================+
|
|---> Object file (.obj) (comd: cc -c <file.name>)
|
V
+=================+
| Linker |
| and |
| loader |
+=================+
|
|---> Executable (.Exe/a.out) (com:cc <file.name> )
|
V
Executable file(a.out)
C预处理是编译的第一步,它处理:
#define
语句。#include
语句。该单元的目的是将C源文件转换为纯C代码文件。
该单元有六个步骤:
它将源文件中的字符组合成“标记”。标记是一组不带有“空格”,“制表符”和“换行符”的字符。因此,这个编译单元也被称为“令牌化器”。它还删除注释,生成符号表和重定位表条目。
这个单元检查代码中的语法。例如:
{
int a;
int b;
int c;
int d;
d = a + b - c * ;
}
=
/ \
d -
/ \
+ *
/ \ / \
a b c ?
因此,这个单元也被称为解析器。
这个单元检查语句的含义。例如:
{
int i;
int *p;
p = i;
-----
-----
-----
}
以上代码生成了"Assignment of incompatible type"错误。
该单元独立于CPU,即有两种类型的优化:
该单元按以下形式优化代码:
例如:
{
int a = 10;
if ( a > 5 ) {
/*
...
*/
} else {
/*
...
*/
}
}
在这里,编译器在编译时知道了'a'的值,因此它也知道if条件语句总是为真。因此,它消除了代码中的else部分。
例如:
{
int a, b, c;
int x, y;
/*
...
*/
x = a + b;
y = a + b + c;
/*
...
*/
}
可以优化如下:
{
int a, b, c;
int x, y;
/*
...
*/
x = a + b;
y = x + c; // a + b is replaced by x
/*
...
*/
}
例如:
{
int a;
for (i = 0; i < 1000; i++ ) {
/*
...
*/
a = 10;
/*
...
*/
}
}
{
int a;
a = 10;
for (i = 0; i < 1000; i++ ) {
/*
...
*/
}
}
在这个阶段,编译器生成汇编代码,使得更频繁使用的变量存储在寄存器中。
在这个阶段,优化是与CPU相关的。例如,如果代码中有多个跳转,则将它们转换为一个:
-----
jmp:<addr1>
<addr1> jmp:<addr2>
-----
-----
控制直接跳转到。
然后,最后一个阶段是链接(创建可执行文件或库)。当运行可执行文件时,所需的库会被加载。
ASCII表示:
[Source Code] ---> Compiler ---> [Object code] --*
|
[Source Code] ---> Compiler ---> [Object code] --*--> Linker --> [Executable] ---> Loader
| |
[Source Code] ---> Compiler ---> [Object code] --* |
| |
[Library file]--* V
[Running Executable in Memory]
希望这能对您有所帮助。
首先,浏览以下图示:
(img source->internet)
您编写一段代码并保存文件(源代码),然后:
预处理:顾名思义,不是编译的一部分。它们指示编译器在实际编译之前执行所需的预处理。您可以将此阶段称为文本替换或解释由#表示的特殊预处理指令。
编译:编译是将用一种语言编写的程序翻译成另一种目标语言的过程。如果存在错误,编译器将检测到并报告。
汇编:汇编代码被翻译成机器代码。您可以将汇编器称为一种特殊类型的编译器。
链接:如果这些代码片段需要链接其他源文件,则链接器将它们链接起来形成可执行文件。
完成这些步骤后还有许多进程。没错,你猜对了,这里就是加载器的作用:
加载器:它将可执行代码加载到内存中;程序和数据堆栈被创建,寄存器被初始化。
附加信息:http://www.geeksforgeeks.org/memory-layout-of-c-program/,您可以在那里查看内存布局。
编译器:它是一种将高级语言程序翻译为机器语言程序的程序。编译器比汇编器更智能,检查各种限制、范围、错误等。但它的程序运行时间更长,占用内存更大,速度较慢。因为编译器会对整个程序进行处理,然后将整个程序翻译成机器代码。如果编译器在计算机上运行,并为同一台计算机生成机器代码,则称为自编译器或常驻编译器。另一方面,如果编译器在计算机上运行,并为其他计算机生成机器代码,则称为交叉编译器。
链接器:在高级语言中,存储了一些内置的头文件或库。这些库是预定义的,包含执行程序所必需的基本函数。这些函数由一个名为链接器的程序连接到库中。如果链接器找不到某个功能的库,则通知编译器,然后编译器生成错误。编译器在编译程序的最后一步自动调用链接器。除了预定义库外,它还将用户定义的功能链接到用户定义的库中。通常,较长的程序被分成较小的子程序,称为模块。这些模块必须组合起来才能执行程序。链接器负责组合这些模块的过程。
加载器:加载器是一种将程序的机器代码加载到系统内存中的程序。在计算机领域,操作系统的一部分是负责加载程序的程序。它是启动程序过程中必不可少的步骤之一,因为它将程序放置到内存中,并准备执行。加载程序涉及将可执行文件的内容读入内存中。加载完成后,操作系统通过将控制权传递给加载的程序代码来启动程序。所有支持程序加载的操作系统都有加载器。在许多操作系统中,加载器常驻内存。
维基百科应该有一个很好的答案,这是我的想法:
来自LinuxJournal的Linkers和Loaders以清晰的方式解释了这个概念。它还解释了经典名称a.out是如何得来的(汇编输出)。
一个快速总结:
c程序 --> [编译器] --> 目标文件 --> [链接器] --> 可执行文件(比如a.out)
我们得到了可执行文件,现在把这个文件给你的朋友或需要该软件的客户 :)
当他们运行此软件时,例如在命令行中输入./a.out
在命令行中执行./a.out --> [装载器] --> [execve] --> 程序被加载到内存中
一旦程序加载到内存中,通过将PC(程序计数器)指向a.out
的第一条指令,将控制权转移到该程序。
它会读取源文件,可能是 .c 或 .cpp 等类型,并将其翻译成称为目标文件(.o 文件)的文件。
它将为多个源文件生成的多个 .o 文件组合成可执行文件(在 GCC 中为 ELF 格式)。有两种类型的链接:
一个程序,可以将可执行文件加载到机器的主存中。
如果您想详细学习 Linux 中这三个程序执行阶段,请阅读此文。
链接器和解释器互相排斥。解释器逐行获取代码并逐行执行。
编译器会检查您的源代码中的错误并将其转换为目标代码。这是操作系统运行的代码。
通常您不会在单个文件中编写整个程序,因此链接器会链接所有目标代码文件。
除非程序在主存储器中,否则无法执行。