精华内容
下载资源
问答
  • 下列不是编译程序组成部分
    千次阅读
    2021-07-21 05:07:22

    现在计算机以及非常普遍了,但是真正了解计算机组成的人却少之又少。下面由秋天网 Qiutian.ZqNF.Com小编为大家整理了计算机操作系统组成部分的相关知识,希望对大家有帮助!

    计算机操作系统组成部分:硬件

    计算机硬件由运算器、控制器、存储器、输入设备、输出设备五部分组成.

    1.运算器是一个用于信息加工的部件,它用来对二进制的数据进行 自述去处和逻辑运算,核心部分是加法器,运算器主要由一个加法器,若干个寄存器和一些控制器组成.

    2.控制器主要功能是根据人们预先编制好的程序,控制与协调计算机各部件自动工作.

    运算器和控制器不论在逻辑关系上或是在工艺上都有十分紧密的联系,往往组装在一起,所以将这两个部分称为“中央处理器”cpu(center processing unit)

    3.1、内存储器

    内存储器(memory主存,内存),是计算机用来存放程序和数据的记忆部件,分为随机存取存储器ram(random access memory)和只读存储器rom(read-only memory)两种.

    ram中的信息:可随机地读出或写入,一旦关机(断电)后,信息不再保存.

    rom中的信息:只有在特定条件下才能写入,通常只能读出而不能写入,断电后,rom中的原有内容保持不变.rom一般用来存放自检程序、配置信息等.

    3.2 外存.

    ①硬盘②软盘③光盘④usb优盘⑤usb移动硬盘⑥dvd光盘

    3.3、高速缓冲存储器(cache memory):是内存与cpu交换数据的缓冲区,是为解决内存与cpu速度不匹配的问题而设计的一种存储设备.

    4、输入设备:把原始数据和处理这些数据的程序通过输入接口输入到计算机的存储器中.

    5.输出设备:输出计算机的处理结果.\x0b 常用输出设备:显示器、打印机、绘图仪、音响、喇叭等(嘴巴)

    计算机操作系统组成部分:软件

    软件是计算机的运行程序和相应的文档。主要包括以下几个部分:

    资源管理

    系统的设备资源和信息资源都是操作系统根据用户需求按一定的策略来进行分配和调度的。操作系统的存储管理就负责把内存单元分配给需要内存的程序以便让它执行,在程序执行结束后将它占用的内存单元收回以便再使用。对于提供虚拟存储的计算机系统,操作系统还要与硬件配合做好页面调度工作,根据执行程序的要求分配页面,在执行中将页面调入和调出内存以及回收页面等。

    处理器管理或称处理器调度,是操作系统资源管理功能的另一个重要内容。在一个允许多道程序同时执行的系统里,操作系统会根据一定的策略将处理器交替地分配给系统内等待运行的程序。一道等待运行的程序只有在获得了处理器后才能运行。一道程序在运行中若遇到某个事件,例如启动外部设备而暂时不能继续运行下去,或一个外部事件的发生等等,操作系统就要来处理相应的事件,然后将处理器重新分配。

    操作系统的设备管理功能主要是分配和回收外部设备以及控制外部设备按用户程序的要求进行操作等。对于非存储型外部设备,如打印机、显示器等,它们可以直接作为一个设备分配给一个用户程序,在使用完毕后回收以便给另一个需求的用户使用。对于存储型的外部设备,如磁盘、磁带等,则是提供存储空间给用户,用来存放文件和数据。存储性外部设备的管理与信息管理是密切结合的。

    信息管理是操作系统的一个重要的功能,主要是向用户提供一个文件系统。一般说,一个文件系统向用户提供创建文件,撤销文件,读写文件,打开和关闭文件等功能。有了文件系统后,用户可按文件名存取数据而无需知道这些数据存放在哪里。这种做法不仅便于用户使用而且还有利于用户共享公共数据。此外,由于文件建立时允许创建者规定使用权限,这就可以保证数据的安全性。

    程序控制

    一个用户程序的执行自始至终是在操作系统控制下进行的。一个用户将他要解决的问题用某一种程序设计语言编写了一个程序后就将该程序连同对它执行的要求输入到计算机内,操作系统就根据要求控制这个用户程序的执行直到结束。操作系统控制用户的执行主要有以下一些内容:调入相应的编译程序,将用某种程序设计语言编写的源程序编译成计算机可执行的目标程序,分配内存储等资源将程序调入内存并启动,按用户指定的要求处理执行中出现的各种事件以及与操作员联系请示有关意外事件的处理等。

    人机交互

    操作系统的人机交互功能是决定计算机系统“友善性”的一个重要因素。人机交互功能主要靠可输入输出的外部设备和相应的软件来完成。可供人机交互使用的设备主要有键盘显示、鼠标、各种模式识别设备等。与这些设备相应的软件就是操作系统提供人机交互功能的部分。人机交互部分的主要作用是控制有关设备的运行和理解并执行通过人机交互设备传来的有关的各种命令和要求。

    虚拟内存

    虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

    用户接口

    用户接口包括作业一级接口和程序一级接口。作业一级接口为了便于用户直接或间接地控制自己的作业而设置。它通常包括联机用户接口与脱机用户接口。程序一级接口是为用户程序在执行中访问系统资源而设置的,通常由一组系统调用组成。

    在早期的单用户单任务操作系统(如dos)中,每台计算机只有一个用户,每次运行一个程序,且次序不是很大,单个程序完全可以存放在实际内存中。这时虚拟内存并没有太大的用处。但随着程序占用存储器容量的增长和多用户多任务操作系统的出现,在程序设计时,在程序所需要的存储量与计算机系统实际配备的主存储器的容量之间往往存在着矛盾。例如,在某些低档的计算机中,物理内存的容量较小,而某些程序却需要很大的内存才能运行;而在多用户多任务系统中,多个用户或多个任务更新全部主存,要求同时执行独断程序。这些同时运行的程序到底占用实际内存中的哪一部分,在编写程序时是无法确定的,必须等到程序运行时才动态分配。

    用户界面

    用户界面(user interface,简称 ui,亦称使用者界面[1])是系统和用户之间进行交互和信息交换的媒介,它实现信息的内部形式与人类可以接受形式之间的转换。

    用户界面是介于用户与硬件而设计彼此之间交互沟通相关软件,目的在使得用户能够方便有效率地去操作硬件以达成双向之交互,完成所希望借助硬件完成之工作,用户界面定义广泛,包含了人机交互与图形用户接口,凡参与人类与机械的信息交流的领域都存在着用户界面。用户和系统之间一般用面向问题的受限自然语言进行交互。目前有系统开始利用多媒体技术开发新一代的用户界面。

    更多相关内容
  • 程序到可执行程序编译过程

    千次阅读 2019-09-19 22:49:47
    一份源代码,从开始产生到成为可执行程序的过程:预处理——编译——汇编——链接。 1、预处理 预处理又叫预编译,主要解释...预处理并不占用运行时间,同时预处理指令本身并不是C语言的组成部分,因此预处理...

    一份源代码,从开始产生到成为可执行程序的过程:预处理——编译——汇编——链接。

    1、预处理

    — 将所有的#defind,ifdef/ifndef删除并且展开

    — 处理所有条件预处理指令

    — 处理#include,将其中包含的文件插入到程序中

    — 过滤掉所有的// 和 /* */ 注释内容

    — 保留所有的#progma编译指令

    预处理并不占用运行时间,同时预处理指令本身并不是C语言的组成部分,因此预处理过程不归入编译器工作范围,而是交给独立的预编译器。

    2、编译

            — 语法分析,

    — 词法分析、

    — 语义分析、

    — 中间代码生成、

    — 代码优化、

    — 目标代码生成 

    编译过程就是将高级语言转换成对应硬件平台的汇编语言,生成.s/.asm文件,不同的处理器有不同的汇编格式,不如X86平台对应x86的汇编格式,ARM平台对应ARM汇编格式。

    编译过程是一段翻译程序,将源程序翻译成另一种语言的目标程序,二者在逻辑上是相当的。

    编译主要的工作就是检验变量、函数、标识符、关键字等使用是否合理;

    具体工作分为六大部分:语法分析词法分析语义分析中间代码生成代码优化目标代码生成

    编译的各个过程中时刻需要表格管理,用来记录源程序的各种各类信息和编译各个子阶段的进展情况。主要的工作是整理符号表,用以等级每个标识符以及它们的属性,比如一个标识符是常量还是变量,类型是什么, 占用内存多少,地址是多少。

    编译的各个过程时刻需要出错管理,用以设法发现错误并将错误信息告知用户。

    出错情况一般包含两方面:语法错误和语义错误

    (1)语法错误

    不合乎语法规则的错误,,比如是否拼写错误,括号是否不匹配

    (2)语义错误

    不合乎语义规则的错误:说明是否有错,作用域是否有错,类型是否有不匹配。

    从源程序到目标程序中产生的中间结果从头到尾扫描一次,产生新的中间结果和目标程序。因此遍数多了,整个编译过程的逻辑结构会非常清晰,但同时会增加编译过程的时间损耗。

    在保证正确和正常运行的情况下,遍数还是少的好。

    3、汇编

    将汇编语言转换成机器语言形式的目标文件,形成.o/.obj/.a/.ko目标文件,这个文件是ELF格式的可重定位文件。

    可重定位文件:该文件中的函数和全局变量还未定位,因为定义和调用可以出现在不同的文件中,因此还需要链接步骤。

    4、链接

    将目标文件.o与启动代码,和相应的静态库或者动态库链接起来,形成.exe可执行文件;

    链接分为静态链接和动态链接。

    之所以需要链接,是因为汇编后的文件可重定位文件还不能直接运行,所以需要链接。

     

     

     

     

    以下是一些衍生出来的问题:

    1、源代码为啥需要编译?

    因为处理器并不能识别由文本字符组成的源文件代码,即使经过预处理后产生的.i文件依旧是文本文件,不能被处理器识别,所以需要编译成能识别的机器码。

    2、编译过程中的6大子阶段的具体工作是什么?

    词法分析:对源程序进行扫描和分析,识别出一个个词,比如:标识符,常量,运算符等等;其运作中遵循的是语法规则(构词规则),描述这套规则的有效工具是正规氏和有限自动机。

    语法分析:在词法分析的基础上,一句语法规则,将词分解成一个个语法单元(短句,子句,句子,程序段,程序),并判断释放符合语法正确性,比如有没有拼写错误,括号有没有不匹配?这是一种层次分析。

    语义分析:对一个个语法单元进行静态语义检查(说明有没有错,作用域有没有错,类型有没有不匹配),分析其含义,判断是否合乎语法范畴,比如变量是否定义、类型是否正确等

    中间代码生成:如果语法分析没错,那就进行初步翻译,产生中间代码。

    代码优化:将代码(主要是针对中间代码)进行加工变换,使其能形成更加高效,更加节省时间和空间的代码。

                        优化的方面:公共表达式的提取、循环优化、删除无用代码。

                       依据的规则是程序的等价交换原则。

    目标代码生成:将经过优化处理后的中间代码转换成特定机器上的低级语言程序代码,比如汇编指令。

    3、一个好的出错管理,应该具备哪些特点?

    (1)全:最大限度发现错误

    (2)准:确定发现错误的性质个准确地址

    (3)局部化:将错误产生的影响压至最小

    (4)自动校正:能尽全力自动矫正错误,但这种能力往往很少。

    4、目标文件由什么组成?

    目标文件由段组成:

    (1)代码段:存放程序的指令,可读不可写,可执行;

    (2)数据段:存放程序中的用到的全局变量和静态数据,可读可写可执行;

    5、编译器是通过判断文件后缀来决定是否编译该文件?

    编译器是根据文件后缀名来辨别是否编译该文件的,.h文件一概忽略,.cpp文件都会被进行编译。

    6、静态链接是指什么?

    程序在进行编译过程中链接静态库(.a/.lib),静态库也会进行静态编译,期间,静态库中所有代码函数(无论是有用的没用的)都被拷进程序,因此编译完后程序体积非常大;

    在运行可执行文件的时候,不再需要静态库的存在了甚至把静态库删了也不影响。

    7、动态链接是指什么?

    程序在编译过程中链接动态库,动态库不会把库的代码拷进程序,而是留下一个标记(位置及名字),告诉可执行程序所需要的库的路径,程序需要就会自己去找,因此程序体积比较轻巧。

    在运行的时候,动态库必须时时刻刻存在,如果被删掉了,该程序就会执行失败。

    8、一个好的编译原理的指标是什么?

    (1)所有合法的程序都能运行;

    (2)非法的错误都能被识别,并产生较少的连锁反应;

    (3)程序出错不至于导致系统奔溃;

    (4)模块化和结构化程度高;

    (5)可维护性和可读性强;

     

     

     

     

     

    展开全文
  • 编译原理】部分题目+知识点

    千次阅读 多人点赞 2021-06-04 18:14:16
    引论 计算机语言分类 机器语言与汇编语言 更接近计算机硬件指令系统的工作 高级语言 更接近求解问题的表示方法 命令语言 控制系统的工作——以功能封装为特征 翻译程序解释程序编译程序区别 翻译程序是将某一种语言...

    引论

    计算机语言分类

    1. 机器语言与汇编语言
      更接近计算机硬件指令系统的工作
    2. 高级语言
      更接近求解问题的表示方法
    3. 命令语言
      控制系统的工作——以功能封装为特征

    翻译程序解释程序编译程序区别

    翻译程序是将某一种语言描述的程序(源程序)翻译成等价的另一种语言描述的程序描述的程序(目标程序)
    解释程序是边解释边执行的,不产生目标程序,速度要慢。
    编译程序则是将高级语言程序翻译成汇编/机器语言程序,计算机直接执行翻译后的程序,速度要快。

    编译系统的总体结构

    词法分析器-> 语法分析器->语义分析和中间代码生成器->代码优化->目标代码生成器
    全过程均有表格管理出错处理

    词法分析的功能是什么?

    词法分析由词法分析器完成,它从左到右扫描源程序——发现一个字符串,则将该字符串转化成单词(token)串;同时查词法错误,进行标识符登记——符号表管理。期间需要删去空格字符和注解等
    输入:字符串
    输出:token(种别码,属性值)序列

    语法分析的功能是什么?

    实现组词成句,识别词组成的各类语法成分,指出语法错误,制导翻译。
    输入:token序列
    输出:语法成分

    语义分析的功能是什么?

    分析由语法分析器识别出的语法单位的语义
    获取标识符的属性:类型、作用域等。
    语义检查:运算的合法性、取值范围等。
    子程序的静态绑定:代码的相对地址
    变量的静态绑定:数据的相对地址

    中间代码的特点?

    简单规范、与机器无关、易于优化与转换

    代码优化

    对中间代码的优化处理:对代码进行等价变换以求提高效率——提高运行速度和节省存储空间(机器有关和机器无关,详见计算机体系结构)。

    目标代码生成

    将中间代码转换成目标机上的机器指令代码或汇编代码

    表格管理是什么

    管理各种符号表(常数、标号、变量、过程、结构……),查、填(登记、查找)源程序中出现的符号和编译程序生成的符号,为编译的各个阶段提供信息。

    错误处理包含哪些内容

    进行各种错误的检查、报告、纠正,以及相应的续编译处理(如:错误的定位与局部化)
    词法:标识符命名是否符合规范等等
    语法:语句结构、表达式结构是否符合
    语义:类型是否匹配

    简单介绍编译的前端和后端

    **前端:**与源语言有关、与目标机无关的部分词法分析、语法分析、语义分析与中间代码生成、与机器无关的代码优化。
    **后端:**与目标机有关的部分、与机器有关的代码优化、目标代码生成。

    编译程序生成可以采用哪些技术

    1. 编译程序的自展技术
    2. 利用编译程序自动生成器

    词法分析

    单词符号有哪些分类

    1. 关键字
    2. 标识符
    3. 常数
    4. 运算符
    5. 界限符 , ; ( ) : =等

    已经可以得知单词在机内表示的方式为一个二元式(单词种别码,单词自身值),那种别码和自身值确定规规则是什么样的?

    种别码
    关键字:一种一码
    标识符:一种码
    常数:一类型一码
    界限符:一符一码
    单词值
    并不是所有的单词都有,关键字、界限符、运算符就没有自身值,常数和标识符的自身值为本身或者符号表入口地址。

    词法分析的过程中,会碰到各种问题,有什么问题以及解决方法是什么?

    1. 词法分析的过程中会有双字符运算符,像是>=,<=,==等,如果仅仅扫描一个字符的时候扫描到>的时候并不能直接判断其为>运算符,需要结合后面一个字符来看,因此需要用到超前扫描下一个字符来判断,解决该问题。
    2. 预处理问题。扫描过程中,剔除源程序里面的无用符号、空格、换行、注释等。
    3. 源程序难免发生错误。首先需要进行一个定位,进行行列计数,再来可以采用恐慌模式,忽略或者替换插入字符继续进行分析等等。

    编译过程中,会维持一张符号表,符号表在编译过程中的作用是什么?

    它用于存储程序编译或运行过程中所使用的变量(标识符)和常量(数字常数、字符常数)等信息。
    词法分析阶段:该阶段主要建立符号表,将不重复的标识符,数字常数和字符常数的性质填入符号表中,并且将变量(常数)在符号表中的入口地址写到自身的token字中。
    语法分析阶段:主要使用符号表。在分析过程中,需要用到某个标识符(或常数)时,就从符号表的指定入口查找出改符号。
    语义分析及中间代码生成阶段:主要是查填符号表。在生成四元式时,通常不是使用变量的名字而是使用它们在符号表的入口地址。另外,在翻译说明语句时,要向符号表中填入变量的类型信息。
    数据存储分配:将变量(或常数)所使用的数据区映像地址写入符号表中的地址(ADDR)栏。若数据区是动态数据,则在符号表中存储过程层号位移量等信息,待运行时再计算具体地址。
    代码优化阶段:使用符号表。一方面遇到变量时,要到符号表中查找它的具体信息,另一方面,在优化过程中,也有可能使用符号表。
    目标代码生成阶段:查找符号表。为最终生成目标代码提供必要的信息。

    符号表内容有什么

    符号表中包括名字(NAME)、类型(TYPE)、种属(KIND)、数值(VAL)、作用域和地址(ADDR)等栏,还带有一个字符串表。

    语法分析

    自顶向下分析面临的问题有什么?

    1. 存在回溯
      文法中每个非终结符的产生式右部称为该符号的候选式,如果有多个候选式左端第一个符号相同,则语法分析程序无法根据当前输入符号选择产生式,只能试探。可以通过提取左公因子改写文法来解决。
    2. 左递归问题
      无法根据左递归文法进行自顶向下的分析。不过可以消除左递归。
    3. 二义性
      可以通过重写文法来消除二义性,引入新的语法变量。
    4. CFG的使用限制
      没有一种方法能够有效地分析所有上下文无关文法,存在无法处理的CFG,且每种方法都有适用范围。

    消除左递归例题

    1. 直接左递归
      E -> E+T | T
      T -> T*F | F
      F -> (E) | id
      消除后
      E -> T E’
      E’ -> +T E’| ε
      T -> F T’
      T’ -> *F T’ | ε
      F -> (E) | id
    2. 间接左递归
      S→Ac|c
      A→Bb|b
      B→Sa|a
      将B的定义代入A的产生式
      A -> Sab|ab|b
      将A的定义代入S中
      S -> Sabc | abc| bc | c
      消除直接左递归:
      S -> abcS’ | bcS’ | cS’
      S’ -> abcS’|ε
      删除多余的产生式A ->Sab|ab|b和B->Sa|a
      结果为 S-> abcS’|bcS’|cS’,S’->abcS’|ε。

    自顶向下的分析方法的基本思想和过程

    基本思想:
    寻找输入符号串的最左推导
    试图根据当前输入单词判断使用哪个产生式
    基本过程:
    从根开始,按与最左推导相对应的顺序,构造输入符号串分析树。
    在这里插入图片描述

    候选式无回溯的条件

    设A→α1|α2|…|αn是所有的A产生式
    如果各个αi能推导出的首终结符各不相同,则可以构造无回溯的分析。自顶向下希望文法满足无回溯。

    LL(1)分析法

    在这里插入图片描述
    在这里插入图片描述

    分析表构造

    1. 对于每一个产生式执行(2)和(3);
    2. 对于FIRST(α)中的每一终结符a,将A -> α填入M[A,a];
    3. 如果ε属于FIRST(α),则对FOLLOW(A)中每一个终结符b,将A-> α填入M[A,b],同时,若$在Follow(A)中,则将A->α,填入M[A,$];
    4. 将所有无定义的M[A,a]标上错误标志。

    题目 构造给定文法的预测分析表

    S -> AB
    A -> Ba | ε
    B -> Db | D
    D -> d | ε
    求出first和follow
    first(D) = {d,ε}
    first(B) = {b,d,ε}
    first(A) = {a,b,d,ε}
    first(S) = {a,b,d,ε}
    follow(S) = {$}
    follow(B) = {a,$}
    follow(D) = {a,b,$}
    follow(A) = {d,b,$}

    abd$
    SS->ABS->ABS->ABS->AB
    AA->BaA->Ba/A->εA->Ba/A->εA->ε
    BB->DB->DbB->Db/B->DB->D
    DD->εD->εD->dD->ε

    由此可见,这个表格是用冲突的,这个文法其实并不是LL(1)文法。

    考题-文法计算题

    去年文法计算题

    判断是否为LL(1)文法,分三步走:

    1. 有左递归不是LL(1)文法(无法对左递归的文法进行自顶向下的分析)
    2. 若A -> α 1 ∣ . . . ∣ α i ∣ . . . ∣ α n \alpha_1|...|\alpha_i|...|\alpha_n α1...αi...αn,则First( α i \alpha_i αi) ∩First( α j \alpha_j αj)=∅ ( i!=j )
    3. α i \alpha_i αi-*->ε(i只能有一个),则对First( α j \alpha_j αj) ∩Follow(A)=∅(i!=j)

    该文法为LL(1)文法。

    1. 首先,文法无左递归
    2. 求First集和Follow集合
      First(S) = {long int}, First(A) = {long int}, First(B) = {id}, First(C ) = {, ε}
      Follow(S) = {$}, Follow(A) = {id}, Follow(B)={$}, Follow(C ) = {$}
      产生式左部为S: ε不包含于First(S),且产生式只有一个,无二义性。
      产生式左部为A: ε不包含于First(A), First(int)∩First(long) = ∅,无二义性。
      产生式左部为B: ε不包含于First(B), 且产生式只有一个无二义性。
      产生式左部为C: ε包含于First(C ),但First(,idC) ∩ Follow(C ) = ∅,并且First(,idC )∩First(ε )=∅。
      所以该文法为LL(1)文法。
      LL分析表如下:
    intlongid,$
    S->AB->AB不应为id不应为,无效句子
    A->int->long不应为id不应为,无效句子
    B不应为int不应为long->idC不应为,无效句子
    C不应为int不应为long不应为id->,idC->ε

    如果再加一个让分析 int a,b 分析过程中中分析栈中元素变化如下。
    首先栈中元素为:$ S next:int,按照分析表弹出S移入AB
    栈中元素为:$ B A next:int,按照分析表弹出A移入int
    栈中元素为:$ B int next:int,此时栈顶元素 = int,弹栈移入下一个符号.
    栈中元素为:$ B next:id,按照分析表弹出B,移入idC
    栈中元素为:$ C id next:id,栈顶元素 = id,弹栈移入下一个符号.
    栈中元素为:$ C next:,,按照分析表弹出C,移入,idC
    栈中元素为:$ C id , next:,,栈顶元素=,,弹栈移入下一个符号
    栈中元素为:$ C id next:id,栈顶元素=id,弹栈移入下一个符号
    栈中元素为:$ C next:$,按照分析表弹出C,移入ε,相当于没移入。
    栈中元素为:$ next:$,栈顶元素=$,分析成功结束。
    所以推导过程如下
    S -> AB -> int B -> int id C -> int id , id C -> int id,id

    介绍自底向上的语法分析技术

    从输入串出发,反复利用产生式进行归约,如果最后能得到文法的开始符号,则输入串是句子,否则输入串有语法错误。核心是寻找句柄。

    最左推导和最右推导

    最左推导:每次推导都施加在句型的最左边的语法变量上。
    最右推导:每次推导都施加在句型的最右边的语法变量上。

    介绍一下句柄

    最右句型(规范规约)中某个产生式体匹配的子串,它的归约代表了该最右句型的最右推导的最后一步;正式定义:如果S=>*αAw =>*αβw;那么紧跟α之后的A→β是最右句型αβw的一个句柄;
    在一个最右句型中,句柄右边只有终结符号
    如果文法是无二义性的,那么每个句型都有且只有一个句柄。

    移入-规约分析法确定句柄的方法

    1. 优先法
    2. 状态法

    LR

    在这里插入图片描述

    LR语法分析器的格局

    LR语法分析器的格局包含栈中内容和余下的输入

    项目集 I 的相容及LR(0)判定

    如果 I 中至少含两个归约项目,则称 I 有归约—归约冲突
    如果 I 中既含归约项目,又含移入项目,则称 I 有移入—归约冲突
    若既没有归约-归约冲突也没有移入规约冲突,则称它为相容的,为LR(0)文法。

    SLR的不足

    SLR只孤立地考察输入符号是否属于归约项目A→α.相关联的集合FOLLOW(A),而没有考察符号串α所在规范句型的“上下文”。

    考题-文法计算题

    第四个文法计算题
    参考博客传送门
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    该题目程序gitee地址:https://gitee.com/loxs/compiler/tree/master/compileEXE1

    语法制导翻译

    语法制导

    上下文无关文法和属性的结合
    属性与文法符号相关联,动作和产生式相关联。
    根据需要将文法符号和某些属性相关联,并通过语义规则来描述如何计算属性的值。

    语法制导翻译

    在产生式体中加入语义动作,并在合适的实际执行这些动作。

    继承属性和综合属性

    综合属性:分析树节点N上的非终结符号A的属性值由N的产生式所关联的语义规则来定义。这个产生式的头一定是A。
    必然通过N的子节点或者N本省来定义。
    继承属性:分析树节点N属性值由N的父节点所关联的语义规则来定义。这个产生式的体中必定包含B。
    依赖于N的父节点,N本身,N的兄弟节点的属性值。

    S属性的SDD

    每个属性都是综合属性
    都是根据子构造的属性来计算整个构造的属性
    在依赖图中总是以子节点的属性值来计算父节点的属性值。可以和自顶向下、自底向上的语法分析过程一起计算。
    自底向上:在构造分析树的子节点同时计算相关的属性。
    自顶向下:在递归子程序法中,在过程A()的最后计算A的属性。

    L属性的SDD

    每个属性要么是综合属性,要么是继承属性且产生式A -> X1X2…Xn中计算Xi.a的规则只能使用
    A的继承属性
    Xi左边的文法符号Xj的继承属性或综合属性
    Xn自身的继承属性和综合属性,且这些属性依赖关系不形成环

    具有受控副作用的语义规则

    有副作用原因:属性文法没有副作用,但是会增加描述的复杂度。
    比如语法分析的时如果没有副作用,符号表就必须作为属性传递。可以把符号表作为全局变量,然后通过副作用函数来添加新标识符。
    受控的副作用:一般属性值计算(基于属性值或常量进行的)之外的功能,不会对属性求值造成约束,即可以按照任何拓扑结构求值,不会影响最终的结果。添加了部分简单约束。

    语法制导的翻译方案

    是在产生式体中嵌入程序片段的上下文无关文法

    后缀翻译方案

    文法可以自底向上分析且SDD是S属性的。
    可以构造SDT,且所有动作都放在产生式的最后;分析过程中按照这个产生式规约时执行这个动作。计算得到的属性值都放在栈中。
    所有动作都在产生式最右端的SDT称为后缀翻译方案

    产生式内部带有语义动作的SDT

    一个动作左部的所有符号(所有符号)处理完成后,就立即执行这个动作。
    B -> X{a}Y
    自底向上分析时,a在X出现在栈顶时执行。
    自顶向下分析时,在试图展开Y或者在输入中检测到Y时执行。
    后缀SDT以及实现L属性的SDT可以在分析时完成。

    L属性的自底向上实现

    首先构造出L属性SDD的SDT,即在非中介符号前计算其继承属性
    若产生式A->α {a} β,则对每个语义动作a,引入标记非终结符号M,且M->ε。
    定义M->ε{a’},其中a’的构造方法如下:
    将a中需要A或者其他属性作为M的继承属性进行拷贝。
    按照a中的规则计算各个属性,作为M的综合属性。
    例子:A->{B.i = f(A.i);}BC
    引入标记非终结符号M后
    A->MBC
    M->ε {M.i = A.i;M.s = f(M.i);}
    A.i为A的继承属性,a的继承属性分析栈BC下方。

    考题-文法翻译题

    三地址代码(偷个懒按照从1开始做了)

    1:if x<3 goto 3
    2:goto 11
    3:if y<2 goto 5
    4:goto 15
    5:if c<1 goto 7
    6:goto 15
    7:t1=x-1
    8:y=t1
    9:goto 3
    10:goto 15
    11:if x>1 goto 13
    12:goto 15
    13:t2=x-2
    14:x=t2
    15:
    

    (2) 11
    (3) 4 6 10 12
    该题目程序gitee地址:https://gitee.com/loxs/compiler/tree/master/compileEXE2

    运行时刻环境

    概述

    运行时刻环境
    确定访问变量时使用的机制
    过程之间的连接
    参数传递
    和操作系统、输入输出设备相关的其他接口

    编译程序除了要关心目标代码生成的问题之外,还必须考虑程序运行时使用的数据区域管理的问题。

    为什么要使用数据区

    程序运行时各种数据都必须放在内存里,便于访问和使用。
    另外涉及数据区的管理:
    内存永远不能满足要求
    必须找到存储的数据
    按照程序单位分配和管理数据区

    数据区存储内容

    变量和常数:用户自己定义
    临时单元:源程序没有,编译程序在生成中间代码时建立和使用
    形式单元:存放过程或函数间传递的参数
    返回地址:返回主调程序的入口
    保护区:保护主调程序寄存器内容,恢复现场
    数据访问控制信息
    存储管理:栈分配、堆管理、垃圾回收

    存储

    目标程序代码放在代码区
    静态区、堆区、栈区分别放不同类型生命期的数据值,即数据区。

    静态和动态存储分配

    静态分配

    编译器在编译时刻就可以做出存储分配决定,不需要运行时刻的情景。
    要求:
    不存在可变长的数组,可变长的字符串、指针。
    不存在函数的递归与嵌套。
    全局变量

    会在符号表中按照对象属性顺序分配数据区,从而建立一个数据映像,运行时按照此映像分配实际的内存数据区。

    动态分配

    栈式存储

    在过程的调用/返回同步进行分配和回收,值的生命期和过程生命期相同。
    有可变数组,有过程和函数的递归嵌套。
    活动树
    过程调用在时间上总是嵌套的:后调用的先返回
    因此可以用栈式分配来处理过程活动所需的内存空间。
    程序运行的所有过程可以用 一个树来表示。

    1. 每个节点对应一个过程活动
    2. 根节点对应main函数的活动
    3. 过程p的某次过程活动对应的子节点对于此次活动调用的各个过程活动。

    过程调用和返回由控制栈进行管理
    每个活跃的活动对于栈中的一个活动记录
    活动记录按照活动的开始时间,从栈底到栈顶排列。

    运行栈:把控制栈中的信息拓广到包括过程活动所需的所有局部信息

    栈中的变长数据:如果数据对象的生命期局限于过程活动的生命期,就可以分配在运行时刻栈中。
    top指向实际栈顶
    top_sp用于寻找顶层记录的定长字段

    访问链
    被用于访问非局部的数据
    如果过程p在声明时嵌套在过程q的声明中,那么p的活动记录中访问链指向最上层的q的活动记录
    从栈顶活动记录开始,访问链形成了一个链路嵌套深度逐一递减。

    如果深度为np的过程p访问变量x,而变量x在深度为nq的过程中声明,那么
    从当前活动记录出发,沿访问链前进np-nq次找到的活动记录中的x就是要找的变量位置。

    x相对于活动记录的偏移量和np-nq在编译时刻已知

    显示表
    背景:用访问链访问数据时,如果嵌套深度大,则访问效率差。
    使用数组d为每个嵌套深度保留一个指针,指针d[i]指向最高的深度为i的活动记录。
    如果程序p中访问深度为i的过程q的变量x,那么d[i]直接指向相应的q的活动记录。
    维护:

    1. 调用过程p时,在p的活动记录中保留d[np]的值,并将d[np]设置为当前活动记录。
    2. 从p返回时,恢复d[np]的值

    考题
    在这里插入图片描述

    堆存储

    在编译是无法确定数据区的大小,在过程的入口处也确定不了数据区的大小,因此无法使用静态或栈式分配方法。
    条件:有static类型,指针类型和表单等变量。
    存储分配器
    为每个内存请求分配一段连续的适当大小的堆空间。首先从空闲的堆空间分配;如果不行则从操作系统中获取内存、增加堆空间。
    回收:把回收的空间返回空闲空间缓冲池,以满足其他的分配。
    评价特性:空间效率(需要的堆空间最小,减小碎片)程序效率(充分使用内存系统的层次,提高效率)、低开销(使分配/收回内存的操作尽可能高效)
    堆空间的分配及管理
    固定长分块法:将堆空间分成带下相同的块,使用链表连接各个单元;适用于类型单一或长度一致的数据类型。
    可变长分块法:将堆空间分成长度不同的块。
    外部碎片和内部碎片问题
    堆空间不可以随便释放,释放方法:参照计数法、无用单元收集。

    代码生成

    主要问题

    正确性
    易于实现、测试和维护
    输入IR的选择
    四元式、三元式、字节代码、堆栈机代码、后缀表示、抽象语法树、DAG图
    输出
    RISC、CISC
    可重定向代码、汇编语言

    目标机模型

    使用三地址机器的模型
    指令

    1. 加载 2. 保存 3. 计算 4. 无条件跳转 5. 条件跳转

    寻址模式
    和汇编中的差不多

    目标代码中的地址

    如何将IR中的名字转换成目标代码中的地址?
    不同区域中的名字采用不同寻址方式。

    活动记录静态分配

    每个过程静态分配一个数据区域,开始位置用staticArea表示

    • call callee的实现
    • callee 中的return

    活动记录栈式分配

    寄存器SP指向栈顶
    第一个过程(main)初始化栈区
    过程调用指令序列
    ADD SP,SP,#caller.recordSize //增加栈指针
    ST 0(SP),#here + 16// 保护返回地址
    BR callee.codeArea// 转移到被调用者
    返回指令序列
    BR *0(SP)// 被调用者执行,返回调用者
    SUP SP,SP,#caller.recordSize//调用者减低栈指针

    基本块和流图

    中间代码的流图表示法

    中间代码划分成基本块,控制流只能第一个指令进入,除了基本块最后一个ie指令,控制流不会跳转停机
    流图的节点是基本块。流图的边指明了哪些基本块可以跟在一个基本块之后运行。

    流图可以作为优化的基础

    它指出了基本块之间的控制流
    可以根据流图了解到一个值是否会被使用等信息

    划分基本块

    确定首指令(基本块的第一个指令)满足下列三条之一即可:
    中间代码的第一个三地址指令
    任意一个条件或无条件转移指令的目标指令
    紧跟在一个条件/无条件转移指令之后的指令
    确定基本块
    每个首指令对应于一个基本块:从首指令(包含)开始到下一个首指令(不含)
    在这里插入图片描述

    一个简单的代码生成器

    重要子函数:getReg(I)
    为三地址指令I选择寄存器;
    根据寄存器描述符和地址描述符、以及数据流信息来分配最佳的寄存器;
    得到的机器指令的质量依赖于getReg函数选取寄存器的算法
    代码生成时必须更新寄存器描述符和地址描述符。

    彩蛋

    试验的翻译方案:选用不同的M可能会避免一些潜在的冲突,但是,也有一些另外的作用,if和if else得选一样的M。
    全局变量

    static int instr=0;  // 指令序号从0开始
    int offset; // 偏移量 - 声明语句对于
    queue<Gen> gens;//指令缓冲队列 便于回填
    

    声明语句

    P -> M1 D S{}
    D -> L id ; N1 D{}
    L -> int
    {
        L.type=1;
        L.width=4;
    }
    L -> float
    {
        L.type=2;
        L.width=8;
    }
    M1 -> ε
    {
        offset = 0;
    }
    N1 -> ε
    {
        table.put(id.lexeme,L.Type,L.width);
        offset = offset+L.width;
    }
    

    赋值和运算语句

    /*
    	mergeType 类型转换&&类型检查 funType(int,float)=float 这种的
    	checkType 检查类型是否匹配
    	调用一次gen,instr++,并且每个指令都有自己的instr编号便于回填
    */
    S -> id = E ;
    {
        checkType(id.Type,E.type);
        gens.push(gen(table[id.lexeme]=E.addr));
    }
    E -> E1 + T
    {
        E.addr = newTemp();
        E.type=mergeType(E1.type,T.type);
        gens.push(gen(E.addr=E1.addr+T.addr));
    }
    E -> E1 - T
    {
        E.addr = newTemp();
        E.type=mergeType(E1.type,T.type);
        gens.push(gen(E.addr=E1.addr-T.addr));
    }
    E -> T
    {
        E.addr=T.addr;
        E.type=T.type;
    }
    T -> F
    {
        T.addr=F.addr;
        T.type=F.type;
    }
    T -> T1 * F
    {
        T.addr = newTemp();
        T.type=mergeType(T1.type,F.type);
        gens.push(gen(T.addr=T1.addr*F.addr));
    }
    T -> T1 / F
    {
        T.addr = newTemp(); 
        T.type=mergeType(T1.type,F.type);
        gens.push(gen(T.addr=T1.addr/F.addr));
    }
    F -> ( E )
    {
        F.addr=E.addr;
        F.type=E.type;
    }
    F -> id
    {
        F.addr=table[id.lexeme];
        F.type=id.type;
    }
    F -> digits
    {
        F.addr=table[digits.lexeme];
        F.type=digits.type;
    }
    

    布尔表达式

    C -> E1 > E2
    {
        C.trueList=makelist(instr);
        C.falseList=makelist(instr+1);
        gens.push(gen(if E1>E2 goto _));
        gens.push(gen(goto _));
    }
    C -> E1 < E2
    {
        C.trueList=makelist(instr);
        C.falseList=makelist(instr+1);
        gens.push(gen(if E1<E2 goto _));
        gens.push(gen(goto _));
    }
    C -> E1 == E2
    {
        C.trueList=makelist(instr);
        C.falseList=makelist(instr+1);
        gens.push(gen(if E1==E2 goto _));
        gens.push(gen(goto _));
    }
    C -> C1 || M7 C2
    {
        backpatch(C.falselist,M1.instr);
        C.truelist=merge(C1.truelist,C2.truelist);
        C.falselist=C2.falselist;
    }
    C -> C1 && M7 C2
    {
        backpatch(C.truelist,M1.instr);
        C.truelist=C2.truelist;
        C.falselist=merge(C1.falselist,C2.falselist);
    }
    C -> ( C1 )
    {
        C.falselist=C.falselist;
        C.truelist=C.truelist;
    }
    C -> true
    {
        C.truelist=makelist(instr);
        gens.push(gen(goto _));
    }
    C -> false
    {
        C.falselist=makelist(instr);
        gens.push(gen(goto _));
    }
    C -> ! C1
    {
        C.falselist=C.truelist;
        C.truelist=C.falselist;
    }
    M7 -> ε
    {
        M1.instr=instr;
    }
    

    控制语句

    S -> if ( C ) M2 S1
    {
        backpatch(C.truelist,M1.instr);
        S.nextlist=merge(C.falselist,S1.nextlist);'
    }
    S -> if ( C ) M2 S1 N2 else M4 S2
    {
        backpatch(C.truelist,M1.instr);
        backpatch(C.falselist,M2.instr);
        S.nextlist=merge(S2.nextlist,merge(N2.nextlist,S1.nextlist));
     }
    S -> while M5 ( C ) M6 S1
    {
        backpatch(S1.nextlist,M1.instr);
        backpatch(C.truelist,M2.instr);
        S.nextlist=C.falselist;gens.push(gen(goto M1.instr));
    }
    S -> S1 M7 S2
    {
        S.nextlist=S2.nextlist;
        backpatch(S1.nextlist,M1.instr);
    }
    M1_7 -> ε
    {
        M1.instr=instr;
    }
    N2 -> ε
    {
        N2.nextlist=makelist(instr);
        gens.push(gen(goto _));
    }
    

    参考博客:https://blog.csdn.net/qq_39384184/article/details/86037568

    展开全文
  • 编译原理绪论

    千次阅读 2020-09-05 21:58:30
    在Microsoft visual C++中编译和运行是分开的两部分。在DEV C++中集成为一个按键。在之前的印象中,编译就是寻找语法错误的过程。只要程序语法有错误,程序就无法通过编译。并会提示相应的信息,告诉写程序的人去...

      之前一直在写程序,了解到运行程序的两个步骤:编译,运行。在Microsoft visual C++中编译和运行是分开的两部分。在DEV C++中集成为一个按键。在之前的印象中,编译就是寻找语法错误的过程。只要程序语法有错误,程序就无法通过编译。并会提示相应的信息,告诉写程序的人去哪里修改什么类型的错误。这学期开始,开设了编译原理课程。按照之前的习惯,通过写博客,及时梳理自己的思路并希望能在某些方面有所提高。下面开始吧!

      学东西先问自己几个问题:什么是编译原理?为什么需要编译原理?怎么才能实现编译原理?

      刚开始学习,对编译原理的理解不是那么深刻。简单谈一谈自己的看法。如果后续学习过程中发现自己理解有误,也会及时修改。

      为什么需要编译原理?
        了解计算机的人都知道对于计算机来说,它所能识别的只是机器代码。就是我们常说的0,1串。但对于编写程序的人来说,如果利用机器语言编写程序,那过程必将是痛苦且低效的。程序员为了让生活对自己好点,慢慢的开发出了高级语言,如C,C++,python等等。抽象来看,输入是高级语言,输出是机器语言,那中间的转化器就是编译器,其原理就是编译原理。现在知道为什么需要编译原理了吧!它是我们和计算机更好沟通的桥梁。

       什么是编译器?
        由之前的问题了解到,编译器的作用是把高级语言转换成低级语言(机器语言)。其实这简单的解释了编译器的定义。运用编译原理编写的程序,并能起到编译作用的程序叫做编译程序。把编译程序做一个推广:翻译程序。翻译程序的定义是:把某一种语言程序(称为源语言程序)转换成另一种语言程序(称为目标语言程序),而两种程序在逻辑上等价。对翻译程序的源语言(高级语言)和目标语言(机器语言)加以限制就成为了编译程序。

      怎么才能实现编译原理?
        问题有点抽象。转换的直接一点:编译程序怎么写?编写程序自然有它的步骤,下面会陆续介绍到编译过程的五大步骤。只有了解编译过程,才能对应去写如何实现这些编译过程的代码。

      回答完刚才的问题,想必对编译原理和编译程序有了少许的了解。下面开始进入正文:通过本篇文章,想解释明白下面几个问题:

      1.编译程序的五大步骤及各步骤的主要工作

      2.有关编译程序的几个名词解释,包括:表格管理、出错处理、遍、编译前端/后端

      3.编译程序的生成,自展和移植。

      注意:因为作者对C语言比较熟悉,所以编译程序的举例都以C语言作为基准。

    一、编译程序的五大步骤及各步骤的主要工作

      先对五大步骤做一个说明,再详细介绍各个步骤:

      1) 词法分析

      2) 语法分析

      3) 语义分析及中间代码生成

      4) 优化

      5) 目标代码生成

      它们的顺序其实已经说明其关系,但为了便于理解,这里附上一张关系图:
    在这里插入图片描述
      下面对各个步骤进行详细说明:

      1) 词法分析

      词法分析就是从头到尾扫描,识别出语法单元,并查看是否存在词法错误。那什么是词法错误呢?举个例子:在部分编程语言中,要求变量名只能由下划线或字母打头而不能使用数字。比如在C语言中的int 3a;就一个不合法的声明,这种错误会在词法分析是被检测出来。再举一个例子,作为编程新手常犯的错误:总是把英文的;写成中文的;这个在编译过程中都会报错,错误的阶段属于词法分析。

      在词法分析的过程中不止会寻找错误,也会对正确的词进行一个分类。包括以下几个类别:

         1. 基本字(保留字):主要指语言中已经定义好的那些字符,例如:int double typedef return等等

         2. 标识符:主要指在程序中定义的变量名。如int a;/double b;中a,b就属于标识符

         3. 界符:界符用于分隔程序,常见的界符有; { } 等

         4. 算符:主要指进行运算的字符,运算包括数值运算和逻辑运算。例如:+,-,*,/和&,|;

         5. 赋值符:完成的功能就是赋值,例如:=,+=,-=等

      举个例子,说明词法分析干的事情:

    在这里插入图片描述

      2) 语法分析

      语法分析主要作用是根据语法规则识别语法单位并分析语句中有没有语法错误(这句话听起来很抽象,啥算语法错误?)识别语法错误,就像a=+c其实本来应该是a=b+c,b没有写,对于词法分析过程只认定出是两个连续的算符。对于加号的语法是左右都要有变量,这就是语法错误。另一个作用是识别语法单位,。举个例子:A=B+C*60;是一个赋值语句,可以构建以下语法分析树,说明这是一个正确的赋值语句:

    在这里插入图片描述  使用语法分析树的好处有什么呢?
        好处在于能够表示出语句的层次结构,同时也可以用于辅助对语句的翻译。
        从下往上看,经历的算符分别是*到+再到赋值符=。这正好符合我们运算过程中对于优先级的体现(想想在运算中是不是先乘除,再加减,最后把结果赋值给左边)。

      对于词法分析和语法分析的总结:词法分析是一种线性分析,语法分析是一种层次结构分析。

      3) 语义分析与中间代码产生器

      语义分析在语法分析之后,所以能到语义分析说明程序中已不存在语法错误。还用之前的a=b+c举例。对于+语义来说,要求左右两个操作数的类型应当相同。对其的检测就属于语义分析的部分。还有一些与具体语言相关的,典型情况包括:变量在使用前先进行声明、每个表达式都有合适的类型、函数的调用和函数的定义相关。(参考博客:https://blog.csdn.net/hit_shaoqi/article/details/83120448link,该博客中列举很多语义分析的错误例子)

      中间代码生成的步骤要先理解什么是中间代码?中间代码就是用简单表达式表示你要进行的操作,举个例子就明白:
      对于A = B+C60;这个表达式生成的中间代码如下:
        temp1 = C
    60
        temp2 = B+temp1
        A = temp2

      对中间代码常用四元式表示,四元式的结构如下:

    在这里插入图片描述
      所以对于上面的中间代码转换为四元式如下:
    在这里插入图片描述

      4) 优化

      优化的意思很明显,就是对代码进行调整使其运行时效率更高(体现在时间/空间上)。优化主要有三个方面:公共子表达式的提取,循环优化(主要优化对象),删除无用代码

      下面对三种优化分别举例:

      公共子表达式的提取优化:
       优化前
         A = B + C;
         …
         D = B + C;
       优化后
         T = B + C;
         A = T;
         …
         D = T;

      循环优化:
       优化前
         for k=1 to 100
          M=I+10k;
          N=J+10
    k;
       优化后
         M=I;
         N=J;
         For k=1 to 100
          M=M+10;
          N=N+10;

      从上面可以看出,循环优化的主要思想是把乘除法优化为加减法。(我们都知道乘除运算所用时间远大于加减法)。

      删除无用代码:
        无用代码就是对程序的结果没有影响的代码。主要可分为两部分

       1. 复制传播
         如果存在语句y=x,并且下面有许多语句在使用y。由于x,y在数值上相等,所以可利用x代替所有的y。这样就可以减少y=x这条语句。这种思想就叫做复制传播。

       2. 无用代码(死code)
         在程序编写过程中,可能出现对一个变量赋值,但后面并没有使用该变量,就认为该赋值无效,将其删除。

      5) 目标代码生成:

      目标代码生成的过程非常复杂,就是把优化好的中间代码转换成指定的低级语言代码(汇编语言或者机器语言等等)。比如上面所举例子最终转换的汇编代码如下图:
    在这里插入图片描述
      从以上五个步骤可以看出一个明显的分界线,就是第三步中语义分析和中间代码生成之间。在中间代码生成之前,都没有对代码的任何翻译,所做的只是分析,故又称为分析部分。语义分析之后都是对代码的翻译调整,故又称为综合部分

    二、有关编译程序的几个名词解释,包括:表格管理、出错处理、遍、编译前端/后端

      表格管理:

        在编译程序中的表格主要的指的是符号表。符号表内存储的内容就是标识符(变量)的各种信息。例如:变量类型,存储位置等等。而对符号表的维护是贯彻在整个阶段的。(即每个阶段对符号表都会增添或删改)

      出错处理:

        在编写程序过程中难免出错,所以对于错误的处理就至关重要。出错可分为两大类:语法错误和语义错误。对于错误处理分几个层次,由坏到好分别为:检测到错误并暂停报错、检测到错误提示报错信息并继续执行程序以发现更多的错误、检测到错误并由对应的办法对错误进行处理校正。

      遍(趟):

        平时总说一遍,两遍。但这里的“遍”定义为对源程序或源程序的中间结果从头到尾扫描一次并作有关的加工处理,生成新的中间结果或目标程序。一遍即可以对一个阶段而言(比如把词法分析单独作为一遍),也可以包括多个阶段(比如把词法分析、语法分析、语义分析和中间代码产生和为一遍)。

      遍的作用是什么?
        “遍”可以使编译程序结构更清晰,每一遍可以集中处理关键问题

      编译前端/后端:

        在概念上一个编译程序可划分为编译前端和编译后端。

        编译前端主要由与源语言有关但与目标机无关的那部分组成的。这些部分通常包括词法分析,语法分析,语义分析与中间代码生成。

        编译后端主要由与目标机有关的那部分组成的。这些部分通常包括优化和目标代码生成。

        那为什么需要划分编译前端和后端呢?

        从上面的概念可以看出,中间代码处于编译前端和编译后端的过渡位置。做这样一个图:
    在这里插入图片描述
      从这个图看出,高级语言变成中间代码这部分叫前端。前端可以由不同的部分引起。同样也可以产生不同的后端。这样不同的前端可以对应相同的后端,相同的前端可以对应不同的后端。就可以很好的体现代码的移植性。对于移植性的介绍会在后面说明。

      在这里解释下为啥会有机器A,机器B的区分。在不同机器上的架构是不同的,据两个方面的例子。一方面是手机和pc机(一大一小,实现方式肯定不同),另一方面是指令集体系结构的不同,像CISC和RISC的实现结果肯定也不相同。

    三、编译程序的生成、自展和移植

      编译程序的生成有三大组成部分:源语言、编译程序、目标语言。编译程序的作用就是把源语言变成目标语言。更为具体:如果想让编译程序在目标机上运行,那么编译程序的编写语言需要是目标语言。(是不是很绕?这语言,那语言的)。为了更方便的说明,做了图形的规定(还是拿图说话!)取名为T形图:
    在这里插入图片描述 (s代表源语言,T代表目标语言,I代表编译程序)

      编译程序的自展

      给定一个目标:在机器A(目标机)上,用语言A(编译程序实现语言),构造高级语言L(源语言)的编译程序。T形图表示为:

    在这里插入图片描述
      Step1:可以考虑使用L的子集S,在机器A上用语言A构造S的编译程序。T形图如下:
    在这里插入图片描述
      Step2:再在机器A上,用语言S构造L的编译程序。T形图如下:

    在这里插入图片描述
      再将两者结合,step1中s可编译成A,step2中可以利用S对L进行编译并在A机器上执行。我们所希望的是L以A的机器语言在A机器上运行,由step1可将S转换成A机器代码,所以L语言就可以用A的机器代码在A的机器上运行(是不是很绕?你如果第一次学那肯定是一脸懵的)我们用图说话:
    在这里插入图片描述
      写完以上过程,我有个疑问:为什么需要s是L的子集?上网找到一种说法发现可以接受,也更为贴切于自展的名字。(下图中的L1就是L的子集,从子集出发才能够慢慢扩展为L)

    在这里插入图片描述
    (节选于博客:https://blog.csdn.net/NameOfBlog/article/details/82857644link

      编译程序的移植:

      什么叫编译程序的移植呢?就是在A机器上已有的高级语言L编写一个在B机器上运行的高级语言L的编译程序。(简单来说就是L目前已经能使用A机器语言在机器A上运行,想要L能使用B机器语言在机器B上运行)T形图如下:
    在这里插入图片描述
      过程简记为一次编程两次编译

       一次编程:使用L语言编写能够让L语言产生B机器代码的编译程序R

       第一次编译:把R在A机器代码下编译使其变为A机器代码的语言,记为编译程序I,作用是把L编译成B

       第二次编译:用编译程序I对R进行编译,成功完成L使用B机器语言完成的汇编程序在机器B上运行。

       就单单这三段话就能让人找不着北!!!我们还是看图说话:
    在这里插入图片描述
      文字配图,事半功倍:

      第一次编程,我们用L语言实现了编译程序R。注意R的本质是L语言,只不过功能是把L翻译成B。既然是L语言,通过已知L->A,我们可以把L语言变成A语言。这里的变成只是实现方式的改变,并没有改变原有的功能,还是一次编程的图,作用还是变成B语言,区别在于原来是用L语言实现的,现在用了A语言。也可以这样理解:

      原来是L1-(L2)->B,又因为L2-(A)->A1。于是L1-(A1)->B。由一次编程知L通过R可变成B,现在只要R变成B就可以了。R的本质是L,第一次编译实现了L变成B,利用第一次的编译程序就可以把L变成B。这样R就变成了B就实现L-(B)->B。

      刚学了绪论,对于编译原理的各个部分了解还过于粗浅。用了将近5000字才完成这篇文章。随着后面的学习,会更深层次的剖析每一个部分的。继续加油哈!

      致谢:感谢编译原理课程谢老师对文章的耐心修改,同样感谢网上各位博主的优秀博文,为我不懂的地方提供解决办法。

    因作者水平有限,如有错误之处,请在下方评论区指正,谢谢!

    展开全文
  • (4) 编译程序的前端:它由这样一些阶段组成:这些阶段的工作主要依赖于源语言而与 目标机无关。通常前端包括词法分析、语法分析、语义分析和中间代码生成这些阶 段,某些优化工作也可在前端做,也包括与前端每个...
  • 答案: 一个典型的编译程序通常包含 8 个组成部分,它们是词法分析程序、语法分析程序、语 义分析程序、中间代码生成程序、中间代码优化程序、目标代码生成程序、表格管理程序和 错误处理程序。其各部分的主要...
  • 编译原理 实验1《词法分析程序设计与实现》

    万次阅读 多人点赞 2019-08-04 16:07:40
    实验1《词法分析程序设计与实现》 一、实验目的   加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行...
  • 编译原理 C语言词法分析程序的设计与实现

    千次阅读 多人点赞 2021-01-04 10:23:45
    词法分析程序 目录 一、 实验题目 二、 实验要求 三、 程序设计说明 四、 源程序 五、 可执行程序 六、 测试报告: 1. 输入 2. 输出 3. 分析说明 一、实验题目 C语言词法分析程序的设计与实现 二、实验要求 ...
  • 编译概述与引论

    千次阅读 2016-07-20 20:02:08
    本博文中,介绍编译程序的基本概念,概述编译过程和编译程序结构,编译程序和程序设计环境以及编译程序的生成过程和构造工具知识。什么叫编译程序通常,我们所说的翻译程序是指这样的一个程序,它能够把某一种语言...
  • 大工20春《编译原理基础》在线作业2满分答案.docx大工20春《编译原理基础》在线作业2满分答案.docx大工20春《编译原理基础》在线作业2满分答案.docx大工20春《编译原理基础》在线作业2满分答案.docx大工20春《编译...
  • 编译程序的工作过程一般可以划分为 词法分析,语法分析,语义分析,之间代码生成,代码优化 等几个 基本阶段,同时还会伴有 表格处理 和 出错处理 . 1-02.若源程序是用高级语言编写的,目标程序是 机器语言程序或汇编...
  • 编译程序:如果源语言为高级语言,目标语言为某台计算机上的汇编语言或机器语言(低级语言),则此翻译程序称为编译程序 解释程序:解释,执行高级语言源程序的程序 源程序:源语言编写的程序为源程序,一般为用高级...
  • 编译原理习题(含答案)

    万次阅读 2020-05-07 08:12:03
    语法分析_1 1 如果文法G是无二义的,则它的任何句子α( )。 A. 最左推导和最右推导对应... SLR(1)分析法 10 编译程序的语法分析器必须输出的信息是( )。 A. 语法规则信息 B. 语法错误信息 C. 语法分析过程 D. 语句序列
  • 计算机组成原理选择题(带答案)

    千次阅读 2021-12-27 13:09:59
    计算机组成原理选择题
  • 1.编译程序的工作过程一般可以划分为 词法分析 , 语法分析 , 中间代码生成 , 代码优化 (可省) , 目标代码生成 等几个基本阶段。 2.若源程序是用高级语言编写的,目标程序是 机器语言程序或汇编程序 ,则其翻译...
  • 编译原理:习题

    千次阅读 2019-06-12 13:07:59
    Q1. 下列哪种语言是依赖于机器的? (10 分)( D ) A. 自然语言 B. 高级语言 C....D....Q2. 编译程序是对( )程序进行翻译?...A....B....C....D....Q3. 编译程序的工作过程按照先后顺序依次划分为_词法分析__,语法分析_,语...
  • 编译原理】实验二 词法分析程序

    万次阅读 2018-03-06 15:39:05
    一、实验标题:词法分析程序 二、实验目的:学习和掌握词法分析程序构造的状态图代码化方法。 三、实验内容:(1)阅读已有编译器的经典词法分析源程序。(2)选择一个编译器,如:TINY或PL/0,其它编译器也可(需...
  • 编译原理知识点

    千次阅读 多人点赞 2019-05-16 09:15:52
    1. 编译程序(编译器):先将源程序翻译成汇编语言程序或机器语言程序(称为目标程序),然后再执行它。 2. 解释程序(解释器):按解释方式进行翻译的翻译程序称为解释程序。解释程序的主要优点是便于对源程序进行...
  • S1|S0|Sa|Sc|a|b|c,下列符号串中是该文法的句子有( )。 A. ab0 B. a0b01 C. a0b0a D. bc10 4 文法G产生的( )的全体是该文法描述的语言。 A. 句型 B. 终结符集 C. 非终结符集 D. 句子 5 若文法G定义的语言是无限集,...
  • 第1 周 程序设计与C语言简介1.1 ...A、编辑程序B、解释程序C、汇编程序D、编译程序2、一个算法应该具有"确定性"等5个特性,下面对另外4个特性的描述中错误的是()。A、有零个或多个输出B、有穷性C、有零个或多个输入D...
  • 编译程序要对程序进行正确的翻译,首先要对程序设计语言本身进行精确地定义和描述。对语言的描述是从三个方面来考虑(精简地说): 语法:是对语言结构的定义(什么样的符号序列是合法的);定义语言的词法和语法的...
  • 编译原理-词法分析

    千次阅读 2019-03-30 14:32:31
    1.2 词法分析程序 1.3 词法分析程序两种实现方式 1.3.1 手工构造 1.3.2 自动生成 1.4 词法分析任务 1.5 词法分析的目的 1.6 词法分析的工作 1.7 两种词法分析调用方式 1.7.1 独立执行一遍 1.7.2 需要时即...
  • 程序生成之编译、链接、加载浅析

    千次阅读 2017-04-28 11:02:35
    程序生成之编译、链接、加载浅析
  • C语言程序设计-中国大学mooc

    千次阅读 2021-05-20 17:48:43
    C语言程序设计-中国大学moochttp://carrottt.blog.bokee.net 2020-6-9转载自网课答案 (www.daanplus.com) :完整...答案:正确2、通常把高级语言源程序翻译成目标程序程序称为( )。A、编辑程序B、解释程序C、汇...
  • 1.随着硬件技术的发展,计算机的电子器件推陈出新,各种类型和用途的计算机也是琳琅满目,但所有种类计算机依然具有“存储程序”的特点,最早提出这种概念的是( C ) A. 贝尔(Bell) B. 巴贝奇(Charles Babbage)...
  • 【其它】请根据下述需求,编写一个程序: 用户输入一个字符串,将下标为偶数的字符串提出来合并成一个新的字符串A,再将下标为奇数的字符串提出来合并成一个新的字符串B,再将字符串A和B连接起来,并输出。加入,我输入了一...
  • CUDA C/C++ 教程一:加速应用程序

    千次阅读 多人点赞 2022-02-19 20:45:51
    文章目录1. CUDA简介2. 准备工作3. 加速系统4. 编写在GPU运行的代码4.1. 编写一个Hello GPU核函数5....近年来加速计算带来了越来越多的突破性进展,应用程序对加速计算日益增长地需求、轻松编写加速计算的程序的.
  • 编译过程的五个阶段

    万次阅读 2018-06-14 14:48:52
    编译程序的工作,从输入源程序开始到输出目标程序为止的整个过程,是非常复杂的。但就其过程而言,它与人们进行自然语言直接的翻译有许多相近之处。当我们把一种文字翻译为另一种文字,例如把一段英文翻译为中文时,...
  • C语言编译的四个步骤

    2021-11-03 10:16:59
    在这篇文章中,我将逐一介绍编译下列C程序的四个阶段。 /* * "Hello, World!": A classic. */ #include <stdio.h> int main(void) { puts("Hello, World!"); return 0; } 预处理 编译的第...
  • Linux编译与调试

    千次阅读 2022-04-05 19:46:25
    SDK就是编译工具链的简写,我们所使用的是gcc系列编译工具链。 使用-v参数来查看gcc的版本,从而确实某些语法特征是否可用,如是否允许使用时声明 $gcc -v 对于.c格式的C文件,可以采用gcc或g++编译 对于 .cc、....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,598
精华内容 17,839
关键字:

下列不是编译程序组成部分