三地址码简介

三地址码简介

三地址码(Three Address Code)是一种最常用的中间语言,编译器可以通过它来改进代码转换效率。每个三地址码指令,都可以被分解为一个四元组(4-tuple)的形式:(运算符,操作数1,操作数2,结果)。由于每个陈述都包含了三个变量,即每条指令最多有三个操作数,所以它被称为三地址码。

编译器

编译器(compiler),是一种计算机程序,它会将用某种编程语言写成的源代码(原始语言),转换成另一种编程语言(目标语言)。

它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序,也就是可执行文件。编译器将原始程序(source program)作为输入,翻译产生使用目标语言(target language)的等价程序。源代码一般为高级语言(High-level language),如Pascal、C、C++、C# 、Java等,而目标语言则是汇编语言或目标机器的目标代码(Object code),有时也称作机器代码(Machine code)。

一个现代编译器的主要工作流程要通常要经过预处理、编译、汇编、链接等步骤。关于编译器的工作流程的介绍,可参考:从C源代码到可执行文件的四个过程:预处理、编译、汇编、链接

中间语言

中间语言(Intermediate language),有时也称为中间表示(Intermediate Rrepresentation,IR)在计算机科学中,是指一种应用于抽象机器(abstract machine)的编程语言,它设计的目的,是用来帮助我们分析计算机程序。这个术语源自于编译器,在编译器将源代码编译为目的码的过程中,会先将源代码转换为一个或多个的中间表述,以方便编译器进行最佳化,并最终产生出目的机器的机器语言。通常,中间语言的设计与一般的机器语言有三个不同之处:

  • 每个指令代表仅有一个基本的操作。举例来说,在微处理器中出现的 shift-add 定址模式在中间语言不会出现。
  • 指令集内可能不会包含控制流程的资讯。
  • 暂存器可用的数量可能会很大,甚至没有限制。

最常见的中间语言表述形式,是三位址码(Three address code),常简称为 TAC 或 3AC。

这个术语也同时用来代称一些作为中间层的语言,有些高级语言不会输出为机器语言,它们仅会输出这种中间语言,而这些中间语言则会像一般语言一样,提交给编译器,编译为机器语言。这通常被用于让最佳化的过程更简单,也用于增进可移植性的能力,改进移植的方式则是利用中间语言的编译器,可以编译出许多中央处理器及操作系统可使用的机器码,例如C语言。中间语言的复杂度,通常介于高阶语言及低级语言之间,例如汇编语言。

四元式

四元式主要由四部分组成:OP,arg1,arg2,result,即(操作符,操作数1,操作数2,结果),

其中,OP是运算符,arg1,arg2分别是第一和第二个运算对象,result是编译程序为存放中间运算结果而引进的变量,常称为临时变量。当OP是一目运算时,常常将运算对象定义为arg1。

例如 X = a*b+c/d 的四元式序列:

  1. (*, a, b, T1)
  2. (/, c, d, T2)
  3. (+, T1, T2, T3)
  4. (=, T3, -, X)
  • 四元式出现的顺序和语法成份的计值顺序相一致。
  • 四元式之间的联系是通过临时变量实现的,这样易于调整和变动四元式。
  • 便于优化处理。

三地址码

三地址代码是四元式的另一种表示形式。每个三地址码指令,都可以被分解为一个四元式(4-tuple)。因为每个陈述都包含了三个变量,所以它被称为三地址码。

上面例子 X = a*b+c/d 的三地址序列:

  1. t1=a*b
  2. t2=c/d
  3. t3=t1+t2
  4. X=t3

常用的三地址码(三地址码形式和四元组形式)

序号指令类型指令形式备注
1赋值指令x = y op zx = op yop为运算符
2复制指令x = y
3条件跳转if x relop y goto nrelop为关系运算符
4非条件跳转goto n跳转到地址n的指令
5参数传递param x将x设置为参数
6过程调用call p,np为过程的名字n为过程的参数的个数
7过程返回return x
8数组引用x=y[i]i为数组的偏移地址,而不是下标
9数组赋值x[i]=y
10地址及指针操作x=&yx=*y *x=y

将上表的三地址指令用四元式表示

x = y op z( op , y , z , x)
x = op y( op , y , _ , x)
x = y( = , y , _ , x)
if x relop y goto n( relop , x , y , n)
goto n( goto , _ , _ , n)
param x( param , _ , _ , x)
call p,n( call , p , n , _)
return x( return , _ , _ , x)
x=y[i]( =[] , y , i , x) ps: y为基地址,i为偏移地址
x[i]=y( []= , y , x , i)
x=&y( & , y , _ , x)
x=*y( =* , y , _ , x)
*x=y( *= , y , _ , x)

每一个指令只有一个操作符,那么只完成一个动作,这样看来,三地址指令序列唯一确定了运算完成的顺序。

中间代码生成的例子

while a<b doif c<5 thenwhile x>y doz=x+1;else x=y;

100到112为指令的编码,从100到112顺序执行。

100: ( j<, a, b, 102 )如果a<b ,那么跳转到102指令,否则继续执行101指令
101: ( j , -, -, 112 )该指令为无条件指令,跳转到112
102: ( j<, c, 5, 104 )如果c<5 ,那么跳转到104指令,否则继续执行103指令
103: ( j , -, - , 110 )该指令为无条件指令,跳转到110
104: ( j>, x, y, 106 )如果x>y ,那么跳转到106指令,否则继续执行105指令
105: ( j , -, - , 100 )该指令为无条件指令,跳转到100
106: ( + , x, 1 , t1 )x+1的值赋值给t1
107: ( = , t1, - , z )t1的值赋值给z,106和107完成了一条语句
108: ( j , -, - , 104 )该指令为无条件指令,跳转到104
109: ( j , -, - , 100 )该指令为无条件指令,跳转到100
110: (= , y, - , x )把y赋值给x,然后执行111指令
111: ( j , -, - , 100 )该指令为无条件指令,跳转到100
112:结束

中间表示IR的演变历史

计算机科学家提出三地址代码的理由如下:三地址代码是一种线性IR。由于输入源程序及输出目标程序都是线性的,因此,线性IR有着其他形式无法比拟的优势。另外,相对于其他表示形式而言,程序员对于线性表示形式通常会有一种莫名的亲切感,编译器设计者当然也不例外。早期编译器设计者往往都是汇编语言程序设计的高手,可以非常自然、流畅地阅读线性的三地址代码形式。同时,线性表示形式也会降低输入输出的实现难度。随着编译器"端"、"遍"等概念的出现,IR已经不仅仅是一种存储在内存中的数据结构。有时它也需要以文件形式转存输出,作为接口供其他系统读取使用。

那么,一定有读者会心存疑问:为什么将其设计为"三地址"的形式呢?实际上,这是计算机科学家经过多年实践探索后才得到共识的。三地址代码并不是唯一的线性IR,只能说是最为常见的而已。在编译技术领域,二地址代码、单地址代码(即栈式机代码)都曾出现过,也曾在某些应用领域盛行一时,尤其是单地址代码。

二地址代码比较简单,就是选择其中一个对象同时充当运算分量与目标操作数。在早期,二地址代码主要就是着眼于x86机器而提出的。不过,实践证明,这只是人们的一厢情愿而已,即使是针对x86机器,二地址的优势也并不明显,它反而可能会给编译器带来一定的麻烦,所以这种表示形式已经逐步被淘汰了。

然而,单地址代码的情况则截然不同了,在现代编译器设计中,单地址代码也是应用比较广泛的一种IR。尤其是近年随着混合语言的日渐壮大,单地址代码也重新进入了人们的视野。由于执行单地址代码程序的栈式机架构相对比较简单,可以非常方便地构造相关的解释器或虚拟机,所以单地址代码深受混合语言设计者的欢迎。读者熟悉的Java字节码、.NET的IL都是单地址代码。栈式机或者单地址代码与常见的x86体系结构相差甚远,可能读者所知不多。不过,单地址代码还是一种比较有意思的表示形式,因此,笔者想通过一个简单的实例让读者对单地址代码有所了解。

三地址代码是在二地址代码的基础上发展而来的。二地址代码的不足之处在于它通常会给其中一个源操作分量带来一定副作用。当然,这种设计的灵感最初是来源于x86指令系统的,但是却忘了一个重要的区别:x86指令中往往都是以寄存器作为暂存空间的。而暂存空间对于二地址代码却是一个棘手的问题。为了解决二地址代码的不足,人们提出了一个对源操作分量不产生任何副作用的形式,那就是三地址代码。也就是说,在一行三地址代码中,任何运算都不会改变两个源操作分量。这是三地址代码与二地址代码的主要区别。这个特性是非常重要的,它将使得编译器更自由地复用名字与值,不必考虑代码带来的副作用。

一般来说,三地址代码的大多数操作都是由四项组成,即一个操作码和三个地址。不过,三地址代码同样存在级别差异。随着语言复杂性的提高,在现代编译器设计中,三地址代码的级别概念显得尤其重要。根据编译器设计的需要,有些三地址代码可能近似于源语言,而有些三地址代码则更接近于目标语言。当然,级别主要就是取决于三地址代码的操作符及操作分量的复杂性。下面,笔者就操作符及操作分量这两个话题来讨论三地址代码。

操作符是用于标识三地址代码操作含义的元素。根据源语言、目标语言的特点,三地址代码操作符的集合以及抽象程度是各不相同的。其中,抽象程度是三地址代码设计中的重要因素之一。一般而言,三地址代码将包含大部分低级操作,即目标机所支持的指令。不过,这并不意味着三地址代码就是机器指令系统的映射。设计者应该从便于后端处理的角度考虑,尽可能地发挥三地址代码作为中间语言的作用

Ref:

https://zh.m.wikipedia.org/wiki/%E4%B8%89%E4%BD%8D%E5%9D%80%E7%A2%BC

https://baike.baidu.com/item/%E4%B8%89%E5%9C%B0%E5%9D%80%E7%A0%81/23121007

https://blog.csdn.net/starter_____/article/details/90146048

https://jishuin.proginn.com/p/763bfbd55cbd

https://book.51cto.com/art/201206/340208.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532651.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

llvm与gcc

llvm与gcc llvm 是一个编译器&#xff0c;也是一个编译器架构&#xff0c;是一系列编译工具&#xff0c;也是一个编译器工具链&#xff0c;开源 C11 实现。 gcc 相对于 clang 的优势&#xff1a; gcc 支持更过语言前端&#xff0c;如 Java, Ada, FORTRAN, Go等gcc 支持更多地 …

攻防世界web新手区解题 view_source / robots / backup

1**. view_source** 题目描述&#xff1a;X老师让小宁同学查看一个网页的源代码&#xff0c;但小宁同学发现鼠标右键好像不管用了。 f12查看源码即可发现flag 2. robots 题目描述&#xff1a;X老师上课讲了Robots协议&#xff0c;小宁同学却上课打了瞌睡&#xff0c;赶紧来教教…

python参数传递*args和**kwargs

python参数传递*args和**kwargs 和* 实际上真正的Python参数传递语法是 * 和 ** 。*args 和 **kwargs 只是一种约定俗成的编程实践。我们也可以写成 *vars 和 **kvars 。就如同其他常规变量的命名一样&#xff0c; args 和 kwargs 只是一种习惯的名称。 *args 和 **kwargs 一…

听GPT 讲Rust源代码--src/tools(25)

File: rust/src/tools/clippy/clippy_lints/src/methods/suspicious_command_arg_space.rs 在Rust源代码中&#xff0c;suspicious_command_arg_space.rs文件位于clippy_lints工具包的methods目录下&#xff0c;用于实现Clippy lint SUSPICIOUS_COMMAND_ARG_SPACE。 Clippy是Ru…

Java一次编译,到处运行是如何实现的

Java一次编译&#xff0c;到处运行是如何实现的 转自&#xff1a;https://cloud.tencent.com/developer/article/1415194 &#xff08;排版微调&#xff09; JAVA编译运行总览 Java是一种高级语言&#xff0c;要让计算机执行你撰写的Java程序&#xff0c;也得通过编译程序的…

JIT(动态编译)和AOT(静态编译)编译技术比较

JIT&#xff08;动态编译&#xff09;和AOT&#xff08;静态编译&#xff09;编译技术比较 转自&#xff1a;https://www.cnblogs.com/tinytiny/p/3200448.html Java 应用程序的性能经常成为开发社区中的讨论热点。因为该语言的设计初衷是使用解释的方式支持应用程序的可移植…

python解释器

python解释器 计算机编程语言 本部分参考自&#xff1a;https://zhuanlan.zhihu.com/p/141212114 从计算机编程语言说起&#xff0c;它主要分为三类&#xff1a;机器语言、汇编语言、高级语言。 机器语言是一种计算机可以直接识别并执行的二进制指令集。由于其可以直接交给…

编译型语言与解释型语言

编译型语言与解释型语言 首先要说明&#xff0c;编译型语言与解释型语言这种分类方法是不科学的&#xff0c;或者说已经过时了&#xff0c;但是这种称呼大抵还是能够让人明白我们将要讨论的是什么东西。 文中所列参考是笔者认为比较有帮助的一些扩展阅读内容。 首先贴一个很形…

常见的各种shell及其区别

常见的各种shell及其区别 引子 for((i1;i<10;i)); do echo $(expr $i \* 3 1); done 网上搜到的 shell for循环脚本&#xff0c;别人都能正常运行&#xff0c;我却报错&#xff1a; Syntax error: Bad for loop variable究竟是怎么回事呢&#xff1f; shell简介…

shell脚本 变量

shell脚本 变量类型 什么是Shell变量 用一个固定的字符串去表示不固定的内容。 Shell变量的类型 shell脚本中自定义变量的类型&#xff0c;我们这里分为&#xff1a; 自定义变量环境变量位置变量与定义变量 这四类&#xff0c;它们有一些相同点&#xff0c;但又有些不同点…

攻防世界web新手区解题 /cookie / disabled_button / weak_auth

cookie 题目描述&#xff1a;X老师告诉小宁他在cookie里放了些东西&#xff0c;小宁疑惑地想&#xff1a;‘这是夹心饼干的意思吗&#xff1f;’ 使用burp suite抓包查看 发现提示&#xff1a; look-herecookie.php 于是在url后加上 cookie.php 得到提示查看返回 就得到了f…

Python 函数式编程

Python 函数式编程 转自&#xff1a;https://www.liaoxuefeng.com/wiki/1016959663602400/1017328525009056&#xff0c;推荐去该链接读原文&#xff0c;有习题和热烈的评论区交流。 函数式编程 函数是Python内建支持的一种封装&#xff0c;我们通过把大段代码拆成函数&…

Python中的生成器与迭代器

Python中的生成器与迭代器 转自&#xff1a;https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640&#xff0c;推荐去该链接读原文&#xff0c;有习题和热烈的评论区交流。 生成器 通过列表生成式&#xff0c;我们可以直接创建一个列表。但是&#xff0c;受…

基于GET报错的sql注入,sqli-lab 1~4

根据注入类型可将sql注入分为两类&#xff1a;数字型和字符型 例如&#xff1a; 数字型&#xff1a; sleect * from table where if 用户输入id 字符型&#xff1a;select * from table where id 用户输入id &#xff08;有引号) 通过URL中修改对应的D值&#xff0c;为正常数字…

Python 装饰器详解(上)

Python 装饰器详解&#xff08;上&#xff09; 转自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84396970&#xff0c;博主仅对其中 demo 实现中不适合python3 版本的语法进行修改&#xff0c;并微调了排版&#xff0c;本转载博客全部例程博主均已亲测可行…

xss原理和注入类型

XSS漏洞原理 : XSS又叫CSS(cross Site Script), 跨站脚本攻击,指的是恶意攻击者往Web页面里插入恶意JS代码,当用户浏览该页时,嵌入其中的Web里的JS代码就会被执行,从而达到恶意的特殊目的. 比如:拿到cooike XSS漏洞分类: 反射性(非存储型) payload没有经过存储,后端接收后,直接…

Python 装饰器详解(中)

Python 装饰器详解&#xff08;中&#xff09; 转自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84581272&#xff0c;博主仅对其中 demo 实现中不适合python3 版本的语法进行修改&#xff0c;并微调了排版&#xff0c;本转载博客全部例程博主均已亲测可行…

存储型xss案例

存储型xss原理: 攻击者在页面插入xss代码,服务端将数据存入数据库,当用户访问存在xss漏洞的页面时,服务端从数据库取出数据展示到页面上,导致xss代码执行,达到攻击效果 案例: 在一个搭建的论坛网站中, 根据存储型xss注入的条件,要找到可以存储到数据库的输入位置,并且这个位置…

反射型XSS案例

**原理:**攻击者将url中插入xss代码,服务端将url中的xss代码输出到页面上,攻击者将带有xss代码的url发送给用户,用户打开后受到xss攻击 需要url中有可以修改的参数 案例: 可能存在反射型xss的功能(点) : 搜索框等&#xff08;所有url会出现参数的地方都可以尝试&#xff09;……

Python 装饰器详解(下)

Python 装饰器详解&#xff08;下&#xff09; 转自&#xff1a;https://blog.csdn.net/qq_27825451/article/details/84627016&#xff0c;博主仅对其中 demo 实现中不适合python3 版本的语法进行修改&#xff0c;并微调了排版&#xff0c;本转载博客全部例程博主均已亲测可行…