编译过程及原理

IT知识
309
0
0
2023-09-11

机器语言

计算机⼤脑或者说⼼脏就是 CPU ,它控制着整个计算机的运作。每种CPU,都有⾃⼰的指令系统。这个指令系统,就是该CPU的机器语⾔。机器语⾔是⼀组由0和1系列组成的指令码,这些指令码,是CPU制作⼚商规定出来的,然后发布出来,请程序员遵守。要让计算机⼲活,就得⽤机器语⾔(⼆级制数)去命令它。这样的命令,不是⼀条两条,⽽是上百条。⽽且不同型号的计算机其机器语⾔是不相通的,按着⼀种计算机的机器指令编制的程序,不能在另⼀种计算机上执⾏。

汇编语⾔和编译器

机器语言编程是不是很令⼈烦恼呢,终于出现了汇编语⾔,就是⼀些标识符取代0与1。⼀⻔⼈类可以⽐较轻松认识的编程语⾔。

只是这⻔语⾔计算机并不认识,所以⼈类还不能⽤这⻔语⾔命令计算机做事情。这正如如何才能

让中国⼈说的话美国⼈明⽩呢?——翻译!

所以,有⼀类专⻔的程序,既认识机器语⾔,⼜认识汇编语⾔,也就是编译器,将标识符换成0与1,知道怎么把汇编语⾔翻译成机器语⾔。

⾼级语⾔

汇编语⾔和机器语⾔都是⾯向机器的,机器不同,语⾔也不同。既然有办法让汇编语⾔翻译成机器语⾔,难道就不能把其他更⼈性化的语⾔翻译成机器语⾔?

1954年, fortran 语⾔出现了,其后相继出现了其他的类似语⾔。这批语⾔,使程序员摆脱了计算机硬件的限制,把主要精⼒放在了程序设计上,不在关注低层的计算机硬件。这类语⾔,称为⾼级语⾔。同样的,⾼级语⾔要被计算机执⾏,也需要⼀个翻译程序将其翻译成机器语⾔,这就是编译程序,简称编译器。这类⾼级语⾔解决问题的⽅法是分析出解决问题所需要的步骤,把程序看作是数据被加⼯的过程。基于这类⽅法的程序设计语⾔成为⾯向过程的语⾔。C语⾔就是这种⾯向过程的程序设计语⾔。

语言的层次

编译过程

编译步骤

  • 词法分析阶段:读⼊源程序,对构成源程序的字符流进⾏扫描和分解,识别出单词。
  • 语法分析阶段:机器通过词法分析,将单词序列分解成不同的语法短语,确定整个输⼊串能够构成语法上正确的程序。
  • 语义分析阶段:检查源程序上有没有语义错误,在代码⽣成阶段收集类型信息
  • 中间代码⽣成阶段:在进⾏了上述的语法分析和语义分析阶段的⼯作之后,有的编译程序将源程序变成⼀种内部表示形式
  • 代码优化:这⼀阶段的任务是对前⼀阶段产⽣的中间代码进⾏变换或进⾏改造,⽬的是使⽣成的⽬标代码更为⾼效,即省时间和省空间
  • ⽬标代码⽣成:这⼀阶段的任务是把中间代码变换成特定机器上的绝对指令代码或可重定位的指令代码或汇编指令代码

编译器

使⽤编辑器编写程序,由编译器编译后才可以运⾏,编译器是将易于编写、阅读和维护的⾼级计算机语⾔翻译为计算机能解读、运⾏的低级机器语⾔的程序。编译器就是将 “⼀种语⾔(通常为⾼级语⾔)” 翻译为 “另⼀种语⾔(通常为低级语⾔)” 的程序。

⼀个现代编译器的主要⼯作流程:

源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → ⽬标代码 ( Object C ode)→ 链接器 (Linker) → 可执⾏程序 (executables)

gcc 编译器介绍

GCC (GNU Compiler Collection,GNU 编译器套件),是由 GNU 开发的编程语⾔编译器。GCC原本作为GNU操作系统的官⽅编译器,现已被⼤多数类 Unix 操作系统(如 Linux 、 BSD 、 Mac OSX 等)采纳为标准的编译器,GCC同样适⽤于 微软 的Windows。

GCC最初⽤于编译C语⾔,随着项⽬的发展gcc已经成为了能够编译C、C++、 Java 、Ada、fortran、Object C、Object C++、 Go 语⾔的编译器⼤家族。

GCC编译流程

编译命令格式:

gcc [-option1] …

g++ [-option1] …

  • 命令、选项和源⽂件之间使⽤空格分隔
  • ⼀⾏命令中可以有零个、⼀个或多个选项⽂件名可以包含⽂件的绝对路径,也可以使⽤ 相对路径
  • 如果命令中不包含输出可执⾏⽂件的⽂件名,可执⾏⽂件的⽂件名会⾃动⽣成⼀个默认名,Linux平台为a.out,Windows平台为a.exe

GCC、g++编译常⽤选项说明

注意事项

Linux编译后的可执⾏程序只能在Linux运⾏,Windows编译后的程序只能在Windows下运⾏。64位的Linux编译后的程序只能在64位Linux下运⾏,32位Linux编译后的程序只能在32位的Linux运⾏。64位的Windows编译后的程序只能在64位Windows下运⾏,32位Windows编译后的程序可以在64位的Windows运⾏。

编译过程

  • 预处理:宏定义展开、头⽂件展开、条件编译等,同时将代码中的注释删除,这⾥并不会检查语法
  • 编译:检查语法,将预处理后⽂件编译⽣成汇编⽂件
  • 汇编:将汇编⽂件⽣成⽬标⽂件(⼆进制⽂件)链接:程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执⾏程序中去

编译过程

分步编译

  • 预处理: gcc -E hello.c -o hello.i
  • 编 译: gcc -S hello.i -o hello.s
  • 汇 编: gcc -c hello.s -o hello.o
  • 链 接: gcc hello.o -o hello

⼀步编译

 gcc hello.c -o demo(还是经过:预处理、编译、汇编、链接的过程) 

汇编语⾔

汇编语⾔的主体是汇编指令, 汇编指令 和机器指令的差别在于指令的表示⽅法上,汇编指令是机器指令的助记符,汇编指令是更便于记忆的⼀种书写格式。它较为有效地解决了机器指令编写程序难度⼤的问题,汇编语⾔与⼈类语⾔更接近,便于阅读和记忆。使⽤编译器,可以把汇编程序转译成机器指令程序。举例如下:

 机器指令:
汇编指令: MOV AX,  BX  

常⻅汇编指令

汇编指令

汇编语言程序

 .data ;此为数据区
sum DWORD ;定义名为 sum 的变量
.code ;此为代码区
main PROC
mov eax, ;将数字3送⼊⽽eax寄存器
add eax, ;eax寄存器加4
mov sum,eax
INVOKE ExitProcess, ;结束程序
main ENDP 

c语言 程序

 int add(int a, int b) {
return a + b; }
int main() {
    return add(, 4);
} 

GCC将这个程序转成汇编语⾔:

 $ gcc -S demo.c 

汇编语⾔程序

 _add:
push %ebx
mov %eax, [%esp+]
mov %ebx, [%esp+]
add %eax, %ebx
pop %ebx
ret 
_main:
push
push
call _add
add %esp,
ret 

C语⾔嵌套汇编代码

 #include <stdio.h>
int main()
{
 //定义 整型变量 a, b, c
 int a;
 int b;
 int c;
 __asm
 {
 mov a, //3的值放在a对应内存的位置
 mov b, //4的值放在b对应内存的位置
 mov eax, a //把a内存的值放在eax寄存器
 add eax, b //eax和b相加,结果放在eax
 mov c, eax //eax的值放在c中
 }
 
  printf ("%dn", c);//把c的值输出
 return;//成功完成
} 

反汇编

c语言代码

 # include  <stdio.h>
int main()
{
 //定义整型变量a, b, c
 int a;
 int b;
 int c;
 a =;
 b =;
 c = a + b;
 
 printf("%dn", c);//把c的值输出
 return;//成功完成
} 

在Go语⾔中也可查看汇编语⾔代码:

 #将Go语⾔程序显示为汇编语⾔
go build -gcflags "-N -l"
go tool objdump -s 'main.Demo' -S ./Go程序.exe