预编译,编译,汇编,链接
Hello World执行的四个步骤
#include <stdio.h>
int main()
{
printf("Hello world\n"):
return 0;
}
使用GCC编译器:
gcc hello.c
./a.out
a.out 含义是 assembler output 即 汇编输出
上述过程可以分解为四个步骤:
1. 预处理(prepressing) gcc -E hello.c 生成 xx.i文件
2. 编译(compilation) gcc -S xx.i 生成 xx.s
3. 汇编(Assembly) gcc -c xx.s 生成 xx.o
4. 链接(Linking) gcc xx.o 生成 a.out
预处理器:会扩展源代码,插入所有用#include命令指定的文件,并扩展所有的宏。
編译器:产生源文件的汇编代码
汇编器:会将汇编代码转化成二进制目标代码文件
链接器:将目标文件与实现标准Uniⅸ库函数(如printf)的代码合并,并产生最终的可执行文件
hello.c例子说明
[root@localhost hello]# ls -l
total 4
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
[root@localhost hello]#
[root@localhost hello]#
[root@localhost hello]# ls -l
total 4
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
[root@localhost hello]# gcc -E hello.c -o hello.i //预处理器
[root@localhost hello]# ls -l
total 24
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
-rw-r--r--. 1 root root 16872 Nov 9 10:00 hello.i
[root@localhost hello]# gcc -S hello.i -o hello.s //編译
[root@localhost hello]#
[root@localhost hello]# ls -l
total 28
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
-rw-r--r--. 1 root root 16872 Nov 9 10:00 hello.i
-rw-r--r--. 1 root root 448 Nov 9 10:00 hello.s
[root@localhost hello]# gcc -c hello.s -o hello.o //汇编
[root@localhost hello]# ls -l
total 32
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
-rw-r--r--. 1 root root 16872 Nov 9 10:00 hello.i
-rw-r--r--. 1 root root 1496 Nov 9 10:01 hello.o
-rw-r--r--. 1 root root 448 Nov 9 10:00 hello.s
[root@localhost hello]# gcc hello.o //链接
[root@localhost hello]# ls -l
total 44
-rwxr-xr-x. 1 root root 8440 Nov 9 10:01 a.out
-rw-r--r--. 1 root root 72 Nov 9 09:56 hello.c
-rw-r--r--. 1 root root 16872 Nov 9 10:00 hello.i
-rw-r--r--. 1 root root 1496 Nov 9 10:01 hello.o
-rw-r--r--. 1 root root 448 Nov 9 10:00 hello.s
[root@localhost hello]# ./a.out
Hello world
[root@localhost hello]#
[root@localhost hello]# head -n 10 hello.i //查看预编译后的文件
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 375 "/usr/include/features.h" 3 4
[root@localhost hello]# cat hello.s //查看汇编文件
.file "hello.c"
.section .rodata
.LC0:
.string "Hello world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits
[root@localhost hello]# head -n 10 hello.o //查看目标文件 -乱码
ELF>?@@
UH?忀耀?]èello worldGCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)zRx
P ?A?C
1. 预处理的工作内容
1.1 预编译作用(处理/扩展所有的宏)
gcc -E hello.c -o hello.i
预编译过程主要处理那些源文件中以“#”开始的预编译指令
比如:#include #define #if #ifdef
结果:生成另外一个c文件,以.i结尾
编译器就是将高级语言翻译成机器语言的一个工具。
1.2 预编译具体工作
1. 将所有的#define 删除,并且展开所有的宏定义
2. 处理所有条件编译指令,如#if #ifdef #elif #else #endif
3. 处理#include预编译指令,将包含的文件插入到该预编译指令的位置,这个过程是递归进行的。
4. 删除所有的注释 // 和 /**/
5. 添加行号和文件名标识,比如 #2 hello.c 2 ,以便调试信息,错误,告警等显示行号
6. 保留所有的#pragma等编译器指令,因为编译器需要他们
结果:经过预编译后的hello.i文件不包含任何宏定义,因为所有的宏已经被展开。
成了另外一个c文件,以.i结尾。
2. 编译的工作内容(产生汇编代码)
编译器(ccl)将文本文件hello.i翻译成文件文件hello.s,它包含一个汇编语言程序。
汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。
gcc -S hello.i -o hello.s
编译过程:编译过程一般可以分为6步:扫描、词法分析,语法分析,语义分析、源代码优化、代码生成、目标代码优化。
1. 扫描:首先源代码被输入到扫描器(Scanner)
2. 词法分析:将源代码的字符序列分割成一系列的记号
记号分为: 关键字,标识符,字面量(数字字符串),特殊符号
3. 语法分析:对记号进行语法分析,产生语法树(Syntax Tree),就是以表达式为节点的树
4. 语义分析:给语法树的表达式标识类型
5. 中间语言生成:简单的代码优化,如 4+5 直接计算结果
6. 目标代码生成与优化:选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等
2.1 汇编代码表示非常接近于机器代码。
与目标代码的二进制格式相比,汇编代码的主要特点是用可读性更好的文本格式表示的。汇编程序员看到的机器与C程序员看到的机器差别很大。
3. 汇编的工作内容(将汇编代码转化成二进制目标代码文件)
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成为一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
hello.o是一个二进制文件,它的字节编码是机器语言指令而不是字符。
gcc -c hello.s -o hello.o
汇编:将汇编代码转化成二进制目标代码文件。
4. 链接(与库文件合并,产生最终的可执行文件)
将目标文件与实现标准Uniⅸ库函数(如printf)的代码合并,并产生最终的可执行文件
链接器负责处理各个文件的链接并入,结果得到hello文件,它是一个可执行目标文件/可执行文件。
可执行文件加载到存储器后,由系统负责执行。
4.1 重定位、符号
重定位(Relocation):重新计算各个目标的地址过程叫做重定位(Relocation)
符号(Symbol):表示一个地址,这个地址可以是一段子程序/函数,也可以是一个变量的起始地址。
4.2 模块调用
模块之间的调用归结为如何通信的问题,常见是模块间的函数调用和模块间的变量访问。
4.3 链接
模块的拼接的过程就是链接
链接(Linking):主要内容就是把各个模块之间的相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
链接的主要过程:包括地址和空间分配,符号决议,重定位
4.4 运行时库
库:其实是一组目标文件的包,就是一些最常用的代码编译成的目标文件后打包存放。
运行时库(Running Library):它是支持程序运行的基本函数的集合
参考
详细参考之前的博客
https://blog.csdn.net/lqy971966/article/details/99769478