C编译器剖析_5.2.5 中间代码生成及优化_赋值表达式的翻译

C编译器剖析_5.2.1 中间代码生成及优化_布尔表达式的翻译 5.2  中间代码生成与优化_布尔表达式的翻译     我们仍然按照语法分析和语义检查时的思路,先讨论表达式的翻译,再处理语句。表达式从概念上来说,可分为算术表达式和布尔表达式,在一些编程语言(例如Java)中对这两者是有严格区分的,算术表达式的结果是整数或浮点数,而布尔表达式的结果是逻辑上的真或假。布尔是英国数学家,由于布尔较早进行了关于“与或非”逻辑运算的研究,为了纪念这位先驱,在Java中 阅读详情

5.2.5 赋值表达式的翻译

    在这一小节中,我们来讨论一下赋值表达式的翻译,例如“a=a-b;”或者“a += b;”等。在图5.2.13的下方我们给出了表达式“a+=b;”在语义检查前后的语法树,右下侧的语法树相当于是表达式a = a’+b,但表达式中的a和a’对应同一个语法树结点。按C的语义,语句“a+=b;”中的a只能被求值一次,而不能求值两次。例如,若a结点对应一个函数调用(*f()),则语句“(*f()) += b;”中的函数f只被调用一次。在图5.2.13的上方是C语句“a = a +b;”的语法树,在该树中有两个语法树结点a,若把a改为表达式(*f()),则我们需要为语句“(*f()) =(*f()) + b;”产生两次对函数f的调用。


图5.2.13  赋值表达式的语法树

    对于赋值语句“a = b;”来说,当a是结构体位域成员时,对a的写操作会较复杂。下面我们还是举一个例子来说明。以下结构体对象dt中的位域b1、b2、b3和b4共占用32位内存(即4字节),其所处偏移为4,在中间代码层次,我们可用符号“dt[4]”来表示该内存单元。

struct  Data{

int  a;     //偏移为0

//以下各成员构成的位模式为“b4_b3_b2_b1”,

//其中,低8位为b1,高4位为b4

intb1:8;   //偏移为4,pos为0

intb2:16; //偏移为4,pos为8

intb3:4;   //偏移为4,pos为24

intb4:4;   //偏移为4,pos为28

int d;     //偏移为8      

} dt;

struct Data * ptr = &dt;

ptr->d = 30;

dt.b2 = val;

   当C程序员要通过“dt.b2 = val;”来对位域b2进行赋值时,我们可按以下步骤来实现:

   (1)按以下中间代码来构造位模式“b4_b3_val_b1”

t1: val << 8;              // LSH指令,左移8位 

t2: t1 & 0x00FFFF00;       //BAND指令,得到位模式“0...0_0...0_val_0...0”

t3:dt[4] & 0xFF0000FF;     //BAND指令,得到位模式“b4_b3_0…0_b1”

t4: t2 | t3;               //BOR指令,得到位模式“b4_b3_val_b1”

   (2)把位模式“b4_b3_val_b1”赋值给dt[4],即可实现对b2的赋值,同时保持b1、b3和b4不会发生变化。

dt[4] = t4;

   当我们面对“ptr->d = 30;”时,UCC编译器为ptr->d产生的中间代码如下所示,由于t7是个临时变量,如果对t7进行赋值,则ptr->num的值并没有发生变化。我们要对t6所指向的内存单元进行赋值,才能改变ptr->num的值。

         t5: ptr

         t6: t5 + 8;                   //成员d的偏移为0

         t7: *t6;

    此时我们可产生形如“(IMOV, t6,30);”的中间代码,其中IMOV表示把30赋值给t6所指向的内存单元,而不是把30赋值给t6。UCC编译器的GenerateIndirectMove函数用于产生IMOV指令。若t6的值存放在寄存器eax中,则最终生成的汇编代码可以是“movl  $30, (%eax)”,对应的中间代码可表示为:

         *t6 = 30;           //而非 t6 = 30;

    有了这些基础之后,我们就可以来分析一下赋值表达式的翻译,如图5.2.14所示。第29至60行的WriteBitFiled函数用于实现对位域成员“dt.b2”的赋值操作,按前文的介绍,对于形如“dt.b2= val;”表达式来说,我们要构造位模式“b4_b3_val_b1”来对符号“dt[4]”进行赋值。图5.2.14第46至50行分别用于产生前文中我们介绍过的“左移LSH”、“按位与BAND”、“按位与BAND”和“按位或BOR”这4条指令,从而得到位模式“b4_b3_val_b1”。当C程序员编写的是形如“ptr->b2=val;”的语句,我们需要在第54行产生一条IMOV指令(形如“*t = val;”)来实现赋值,这需要进行间接寻址;而当C程序员编写的是形如“dt.b2 = val;”的语句时,我们在第57行产生MOV指令(形如“dt[4] = val;”)即可,不必进行间接寻址。对赋值表达式“dt.b2 = val”来说,虽然按C的语义,整个表达式“dt.b2 = val”不可充当左值来使用(即“(dt.b2= val) = 3;”是非法的),但赋值操作后,dt.b2的值即为整个表达式的值。因此,我们在第59行调用函数ReadBitField来读取dt.b2的值,用来作为整个表达式“dt.b2 = val”的值。当“dt.b2 = val;”中的val为常数时,我们可在编译时进行一些常量折叠的处理,避免生成一些不必要的指令,函数WriteBitFiled中的其他代码主要用于这些情况,结合图中的注释不难理解相关代码,我们就不再啰嗦。


图5.2.14  TranslateAssignmentExpression()

    当我们面对的是形如“a+=b;”的赋值表达式时,UCC编译器在图5.2.14第5行调用函数TranslateExpression,递归地对与a对应的左子树进行翻译。由图5.2.13右下方的语法树,为了避免对a对应的子树进行重复翻译,我们在第8行把左子树a的根结点当作一个标识符结点OP_ID,这样即使当a为(*f())时,我们也只会产生一次函数调用f。另一个需要特殊处理的是当a为“dt.b2”这样的位域成员时,若我们面对的是形如“dt.b2 += b;”这样的赋值表达式,则图5.2.14第5行真正调用的函数是我们在上一节图5.2.11中介绍的函数TranslateMemberAccess。当位域成员dt.b2处于赋值号的左侧时,在TranslateMemberAccess函数中,我们并未调用ReadBitFiled进行读操作,因此在图5.2.14第10行我们还要对此进行判断。对于“dt.b2 += b;”而言,当我们第一次访问到dt.b2的子树时,在图5.2.14第5行的函数调用返回后,dst对应的符号为“dt[4]”。由于dt.b2是位域,我们需要在第11行调用ReadBitFiled函数进行读操作,这将产生以下中间代码,其中符号“t2”就保存了dt.b2的值,我们需要在第9行把符号“t2”保存在dt.b2对应子树的根结点,当我们第二次访问到dt.b2对应的子树时,其根结点已被当成一个标识符结点OP_ID,其符号名为“t2”,这样我们就可在稍后产生形如“t3: t2+b”的加法指令。

         t1 : dt[4] << 8;

         t2 : t1 >> 16;   

    图5.2.14第13行递归地调用TranslateExpression来翻译赋值运算符的右子树,第15行调用WriteBitFiled来实现对位域成员的写操作,第16至22行用于处理形如“ptr->d=30;”的赋值运算,我们需要在第21行产生形如“*t = 30;”的IMOV指令用于间接寻址,而第25行产生形如“a = b;”的MOV指令。

    在此基础上,我们再来理解“a++”或“++a”这样的运算就轻松了许多,图5.2.15给出了这两个表达式在语义检查后的语法树。若a在执行自加运算前的值为val,按照C语言的语义,经过a++和++a的运算后,a的值都变为(val+1),但表达式“a++”的值为val,而表达式“++a”的值为(val+1)。需要注意的是,在C语言中,“a++”和“++a”都不可以作为左值来使用,即“(a++) =3;”和“(++a) = 5;”都是非法的;但在C++语言中,“++a”可充当左值使用,即“(++a) = 5;”是合法的,但“(a++) =3;”是非法的。这也是C语言不是C++语言子集的一个例证。


图5.2.15  a++和++a的语法树

    结合图5.2.15,我们可以来理解一下对自加运算进行翻译的函数TranslateIncrement,如图5.2.16所示。按照我们在图5.2.14对“a+=b”的翻译,对于“++a”来说,我们递归地调用TranslateIncrement(expr->kids[0])即可完成翻译,这可通过图5.2.16第5行和第22行来实现。但对于“a++”来说,我们要先计算出图5.2.15结点a的值,该值即为整个表达式“a++”的值,之后再进行“a+=1”的运算来使a的值加1。图5.2.16第6行递归地调用TranslateExpression函数完成了对a对应子树的翻译,第7行把该子树置为标识符结点OP_ID,也是为了避免对a子树的多次求值。对于“a++”或“a--”而言,当结点a为位域成员时,我们需要通过第14行的ReadBitField函数来读取该位域的值;在a结点不是形如“t1:*t2”的临时变量时,我们在第16和17行创建一个临时变量来保存表达式“a++”的值;否则,我们面对的是形如“ptr->d”的非位域成员,此时第13至18行的if条件都不成立,我们在第12行即已保存“t1: *t2;”中的符号t1。接下来,在第19行真正调用的函数是TranslateAssignmentExpression,用于实现对结点a的加1运算,第20行返回“a++”整个表达式的值。


图5.2.16  TranslateIncrement()

   在下一节中,我们来分析一下tranexpr.c中的其他函数,例如一元运算表达式的翻译。
C编译器剖析_5.2.2 中间代码生成及优化_再论符号symbol与公共子表达式 5.2.2    再论符号symbol与公共子表达式     在介绍算术表达式的翻译前,让我们简单重温一下第2.5节中的“图2.5.4 公共子表达式”及“图2.5.5 valueDef和valueUse”。为阅读方便,我们再次给出这两张图,更详细的说明请参见第2.5节。对于图2.5.4第2行的a+b,我们会由第7行的中间代码来对a+b进行求值,其结果存于临时变量t1中,之后在第3行中再次遇到表达 阅读详情

相关推荐

从零解析C语言编译器:源码结构与核心模块实现详解

编译器是连接高级编程语言与计算机硬件的关键系统软件,其核心原理是将源代码转换为机器可执行的目标代码。这一过程通常遵循经典的编译流程,包括词法分析、语法分析、语义分析、中间代码生成、优化和目标代码生成等阶段。理解编译器的实现,对于深入掌握程序运行机制、提升系统编程能力以及参与大型编译器项目开发具有重要价值。在工程实践中,通过剖析一个中等规模、功能完整的教学型编译器源码,可以将抽象的编译原理概念(如有限自动机、递归下降解析、符号表管理)与具体的C语言代码实现一一对应。本文聚焦于一个用C语言实现的简单C编译器项目

weixin_33811539的博客 403

编译原理 4 - 语义分析、中间代码生成、代码优化、代码生成

编译原理笔记 - 4 - 语义分析、中间代码生成、代码优化、代码生成

qq_50918834的博客 1321

编译原理实验:中间代码优化实践报告

基本块定义了一组最小的独立执行单元,这使得它们成为编译优化中理想的局部优化候选。基本块内不存在跳转语句(除了最后一个语句可能是一个跳转),这意味着基本块内的任何指令都不会被跳过,且执行流在进入基本块后,必然会按顺序执行完全部指令。在代码优化中,基本块允许编译器工程师对代码进行局部分析和变换而不必担心复杂的控制流影响。例如,通过基本块分析,可以进行死代码删除、常量传播、部分冗余消除等优化操作。由于这些变换是在局部范围内进行的,所以相对容易实现,并且能够保证变换的安全性,即不会改变程序的语义。

weixin_36235398的博客 941

[编译原理] 中间代码生成 代码优化

编译原理 —— 代码优化

Tumb1eweed的博客 1655

编译原理中间代码生成实验报告——完整版

完整的编译原理实验报告 关于语法、语义和词法分析器三部分的 很全哦 一、实验题目 表达式中间代码生成 二、实验目的 熟悉算术表达式的语法分析与中间代码生成原理。 三、实验内容 1. 构造算术表达式的四元式翻译文法 2. 设计算术表达式的递归下降子程序分析算法 3. 设计算术表达的四元式生成算法 4. 实现代码并调试运行 四、实验要求 1、采用递归下降语法制导翻译法,对算术表达式,赋值语句进行语义分析并 生成四元式序列. 2、输入是语法分析后提供的正确的单词串,输出四元式序列. 例如:对于语句串 Begin a:=2+3*4;x:=(a+b)/c end# 输出的指令如下: (1) t1=3*4 (2) t2=w+t1 (3) a=t2

编译原理中间代码生成

这个是编译原理课的实验,采用c++实现,做的是语法分析后形成中间代码。

【编译原理】中间代码优化(一) 优化技术大观

优化其实可以在编译的各个阶段进行,但最主要的一类优化是在目标代码生成以前,对语法分析、语义分析后产生的中间代码进行优化。这是因为中间代码的形式不依赖于具体的计算机,它可以是三地址码的形式,所以相应的对于中间代码的优化也不依赖于具体的计算机。另一类优化是在生成目标代码时进行的,它很大程序上依赖于具体的计算机。中间代码的优化中,有很多技术和手段可以应用。

Esperanto.的博客 7961

中间代码生成代码(中缀表达式转换为四元式)

Python实现的编译原理中间代码生成程序,使用了PyQt5写图形界面 题目:设计一个程序,该程序能够将形如x=y op z的简单赋值语句翻译为对应的四元式序列,其中op可为+、-、*、/等二元运算符。要求用图形界面方式编程. 例如:若输入赋值语句a=b+c,则输出如下四元式序列: (+,b,c,t1) (=,t1,-,a)

C编译器剖析_5.2.6 中间代码生成及优化_一元表达式及其他表达式的翻译

5.2.6  一元表达式及其他表达式的翻译      在这一小节中,我们先来讨论一下一元表达式的翻译,我们先举个例子来说明一下。在以下C程序中,符号arr被声明为int[3][5]的数组类型,UCC编译器在语义检查后,为表达式“**arr=30;”构造的语法树为“(= ([]([] arr  0) 0) 30)”,在中间代码生成阶段,对应的中间代码为“arr[0] = 30;”。由这个例子可见,

SheIsC的专栏 1298

中间代码生成

花了很长时间,从词法分析,用预测分析表实现语法,到表达式生成中间代码,后来生成if和while语句的中间代码,终于可以截稿了。

深入解析Cuk、Zeta和Sepic拓扑:非隔离型开关电源的设计奥秘

本文深入解析了Cuk、Zeta和Sepic三种非隔离型开关电源拓扑的设计奥秘。它们均能实现升降压转换,但特性各异:Cuk拓扑输出反向电压,输入输出纹波极低;Sepic拓扑输出同极性电压,应用广泛;Zeta拓扑输出纹波小,适合精密供电。文章详细对比了其电路结构、工作原理、核心公式及选型指南,帮助工程师根据输入电压范围、输出极性及纹波要求,灵活选择最合适的拓扑方案。

weixin_28308325的博客 476

C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译

5.2.4 后缀表达式的翻译     在前面的章节中,我们介绍了用于对数组元素和结构体成员进行访问的函数Offset,其接口如下所示,参数addr代表了基地址,参数voff代表可变偏移,而参数coff则代表常量偏移。          Symbol Offset(Type ty, Symbol addr,Symbol voff, int coff);     函数Offset的基本想法是产生

SheIsC的专栏 1566

STM32H743必要外围电路分析

写在前面:这篇博客是我第一次绘制STM32最小系统板的过程记录。 2020-1-11 BOOT STM32采用ARM内核,和ARM处理器一样,都有专门的boot脚决定单片机从何处启动。 在官方数据手册的第105页,我们可以看到 系统复位后,在SYSTICK的第四个上升沿锁存BOOT引脚的值,复位后,BOOT引脚可以由用户自由配置而不会影响系统正常运行。 BOOT引脚决定了自举存储器地址,当BO...

Cui_Hongwei的博客 1万+

编译原理实战:从声明到控制流,中间代码生成全解析

本文深入解析编译原理中的中间代码生成过程,从声明语句到控制流翻译,详细介绍了三地址码的生成与应用。通过实际案例展示如何将高级语言转换为平台无关的中间表示,帮助开发者理解编译器内部工作原理,优化代码生成与调试效率。

weixin_29224309的博客 352

SAP 角色继承、派生

角色继承、派生 继承:是指两个角色Role形成母子关系。子角色的所有菜单、授权都源于母角色,并与母角色保持一致。母角色与子角色是1对多的关系。 继承关系:新建子角色继承母角色中的事务(后续通过复制,将母角色授权数据复制至子角色) 派生概念:将母角色中的授权数据派生至子角色中。 业务场景:方便批量修改子角色权限。若有继承关系,只需要修改母角色,然后通过母角色派生功能,从而批量修改子角色授权数...

willieyuan的博客 1万+

语音识别:PyAudio、SoundDevice、Vosk、openai-whisper、Argos-Translate、FunASR(Python)

本文介绍了Python中常用的音频处理库PyAudio,详细解析了其核心功能、核心概念和典型工作流程。PyAudio是一个跨平台的音频I/O库,支持音频录制、播放、实时流处理和低延迟操作。核心概念包括PyAudio类(管理音频设备和流)和Stream类(代表音频流)。文章重点讲解了open()方法的关键参数、回调函数的使用方式以及典型的工作流程,包括设备查询、流配置和资源释放。PyAudio适用于需要实时音频处理的场景,如语音识别和音频效果处理。

算法工程师 705

万字长文!带你吃透语义分析与中间代码生成(七)——语法制导翻译实战:从赋值到控制流

本文深入探讨了语法制导翻译在编译原理中的应用,详细解析了从赋值语句到控制流的语义分析与中间代码生成过程。通过实战案例展示了三地址代码生成、布尔表达式优化、控制语句翻译等关键技术,帮助开发者掌握编译器前端设计的核心方法。

weixin_34326429的博客 410

Z-Image Turbo随机性增强插件的下载教程及使用方法 z-image生成图像太相似怎么办 Z-Image Turbo 整合包下载

Z-Image Turbo 是出了名的“seed 不敏感”,非常难做多样化图像创作。ComfyUi-ConditioningNoiseInjection 插件通过在conditioning(提示词编码)阶段加入可控噪声,成功解决了 Turbo 随机性不足的问题。它不仅能让 Turbo 产生更丰富的构图差异,还能保持主题稳定,是目前我测试过最有效的 Turbo 随机性增强工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1975

二.编译器和代码优化技术

编译器是将高级编程语言(如C、C++、Java、Python)编写的源代码,转化为计算机可直接执行的机器语言(或字节码、中间代码)的程序,核心价值是“翻译+优化”,让代码能高效在硬件上运行。词法分析:拆分源代码为最小语法单元(如关键字、变量名、运算符,称为“Token”),比如把int a=1+2;拆为inta12;。语法分析:基于Token构建抽象语法树(AST),校验语法合法性(如是否漏写分号、括号不匹配),AST是源代码的结构化表示,便于后续处理。语义分析。

cscs2020ll的博客 855

51单片机+L298驱动步进电机+L297测速+LCD1602显示+按键调试+Proteus仿真

51单片机+L298驱动步进电机+L297测速+LCD1602显示+按键调试+Proteus仿真 *Proteus仿真 L298N是双H桥式驱动器。 L297是步进电机控制集成芯片(包括环形分配器),可产生四相驱动信号,应用于微处理机控制两相双极性和四相单极性步进电机。电动机可由半阶梯、正常和斩波驱动模式驱动,同时设于晶片内的PWM斩波线路容许以开关形式控制线路的电流。此器件只需要时钟、方向和模式输入信号。相位是由内部产生的,因此可减轻微处理机和程序设计的负担。 实例代码 /************

perseverance51博客 4584

多天线技术:5G波束成形&空间复用&空时编码

以提升信号的可靠性。然而,在20世纪90年代中期,研究者们惊讶地发现,多天线技术不仅仅提供分集增益,还可以开启一些单天线系统无法实现的新机遇。,我们可以将信道矩阵 ( H ) 视为一个满秩矩阵,则理论上可以建立min(𝑁𝑇,𝑁𝑅)个独立的数据流,形成一个。波束成形是5G物理层的核心技术,其本质是利用多个天线来增强特定方向上的信号,同时抑制干扰方向的信号。:物理波束成形 = 🔦 类似手电筒的聚焦光束,虚拟波束成形 = 🌊 类似水波的相干叠加。随着 5G 和 6G 时代的到来,多天线技术的作用愈发重

weixin_43199439的博客 1389

编译原理实战:从SDT到三地址码的中间代码生成

本文深入探讨了编译原理中从语法制导翻译到三地址码生成的实战过程。详细解析了如何利用SDT为变量声明、赋值表达式及控制流语句生成中间代码,并重点介绍了高效处理控制流跳转的回填技术,为编译器后端优化与目标代码生成奠定坚实基础。

weixin_29215815的博客 100

《网络安全自学教程》- SQL注入之报错注入原理+步骤+实战操作

这篇文章为大家解析报错注入的实现原理,结合实战案例讲解报错注入的使用步骤。

wangyuxiang946的博客 1万+

从赋值语句到三地址码:递归下降翻译法的实战解析

本文深入解析递归下降翻译法在编译原理中的应用,从赋值语句到三地址码的转换过程。通过实战案例展示语义动作嵌入时机、三地址码优化技巧及错误恢复机制,帮助开发者掌握高效代码生成方法。特别适合从事语义分析和编译器设计的工程师参考。

weixin_29216957的博客 218

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Cuik编译器架构详解:从词法分析到代码生成的全流程解析

Cuik是一款现代化的C11编译器,虽然仍处于早期开发阶段,但已展现出高效的编译流程设计。本文将深入剖析Cuik编译器的核心架构,从预处理到代码生成,全面解读其工作原理与技术细节。 ## 一、Cuik编译器的整体架构设计 Cuik编译器采用模块化设计,主要由四大核心组件构成,各组件通过明确的接口协同工作,形成完整的编译流水线。 ### 1.1 核心组件概览 Cuik的架构遵循经典的编译器设

gitblog_00486的博客 738

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

编译原理实践:基于递归下降的表达式语义分析与四元式生成

本文深入探讨了编译原理中递归下降分析法在表达式语义分析与四元式生成中的实践应用。通过详细的代码示例和实战解析,展示了如何实现运算符优先级处理、临时变量管理以及错误恢复机制,为开发者提供了一套完整的编译技术解决方案。

weixin_33737134的博客 73

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译
下一篇: C编译器剖析_5.2.6 中间代码生成及优化_一元表达式及其他表达式的翻译
SheIsC
博客等级 码龄12年 224粉丝 64原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值