精华内容
参与话题
问答
  • 编译的基本概念

    千次阅读 2019-07-10 14:33:39
    CPU执行程序的原理这篇文章中提到,“程序要想被CPU执行,首先要被编译成CPU可以执行的指令操作”,那编译成CPU可以执行的指令操作是什么意思呢?这篇文章就用来说明编译的实际意义是什么。 2、知识背景——CPU架构...

    1、本文目的

    CPU执行程序的原理这篇文章中提到,“程序要想被CPU执行,首先要被编译成CPU可以执行的指令操作”,那编译成CPU可以执行的指令操作是什么意思呢?这篇文章就用来说明编译的实际意义是什么。

    2、知识背景——CPU架构

    要谈编译,首先要说说CPU架构的概念。CPU架构也就是CPU指令集(指令就是汇编指令或者机器指令,比如Add是汇编指令,而对应的机器指令在MIPS下就是000000)架构,现有CPU架构包括鼎鼎有名的Intel的X86架构、ARM的ARM架构、MIPS的MIPS架构、DEC的Alpha架构。通俗来说,指令集就是指挥CPU如何运算的硬程序,没有这套指令的话,就没有办法指挥CPU运转,而计算机的所有运算都需要CPU参与。

    那编译呢,也就是将一段程序转换为指令集的过程。不同架构的指令集自然是不同的,带来的影响就是同一段代码,编译过后只能运行在对应的指令集上,比如一段C++代码,在X86下编译完了,只能在X86下运行,而不能运行在ARM架构下运行。

    而事实上,编译得到的结果,更是操作系统相关的。假设,一段程序被编译成了X86下的硬程序,但是无法同时运行在Windows上和Linux上(Windows和Linux操作系统都可以装在X86架构的CPU上),如果程序一开始是在Windows操作系统下编译的,那这段程序就无法运行在其他比如Linux操作系统中。

    也就是说,编译与操作系统和CPU这二者都是相关的。

    3、编译过程

    事实上,仅仅将程序通过编译改写成汇编指令或机器指令,在操作系统上还不能直接运行。实际上广义的编译,其实包括预处理、编译、汇编、链接这整个过程。

    1. 预处理,就是把代码里引入的其他代码,插入到这段代码中,形成一个代码文件。
    2. 编译,就是把代码转化为汇编指令的过程,汇编指令只是CPU相关的,也就是说C代码和python代码,代码逻辑如果相同,编译完的结果其实是一样的。
    3. 汇编,就是把汇编指令转为机器码的过程,机器码可以被CPU直接执行。
    4. 链接,就是将一段我们需要的已经编译好的其他库,与我们的汇编结果连起来,这样才是最终程序完整的形式,操作系统才可以运行。不同操作系统编译好的其他库形式不同,而且链接的方式也不同,得到最终程序的形式也不同,所以编译好的程序只能在特定的操作系统下运行。
    展开全文
  • 我们的代码会经过这4个环节,从而形成最终文件,c语言作为编译语言,用来向计算机发出指令。让程序员能够准确地定义计算机所需要使用的数据,并精确地定义在不同情况下所应当采取的行动。 预处理, 展开头文件/宏...

    楔子

    我们在各自的电脑上写下代码,得明白我们代码究竟是如何产生的,不想了解1,0什么的,但这几个环节必须掌握吧。

    我们的代码会经过这4个环节,从而形成最终文件,c语言作为编译语言,用来向计算机发出指令。让程序员能够准确地定义计算机所需要使用的数据,并精确地定义在不同情况下所应当采取的行动。

     

    预处理, 展开头文件/宏替换/去掉注释/条件编译                      (test.i main .i)
    编译,    检查语法,生成汇编                                                      ( test.s  main .s)
    汇编,   汇编代码转换机器码                                                         (test.o main.o)
    链接     链接到一起生成可执行程序                                              a.out
     

    预处理

    预处理如锲子中所言,是一种展开,下表是常用的一些预处理命令

    还有下列几种预处理宏(是双下划线)

    __LINE__ 表示正在编译的文件的行号
    __FILE__表示正在编译的文件的名字__DATE__表示编译时刻的日期字符串,例如: "25 Dec 2007"
    __TIME__ 表示编译时刻的时间字符串,例如: "12:30:55"
    __STDC__ 判断该文件是不是定义成标准 C 程序
    我的vs2013不是定义的标准c语言

     

    宏函数很好用,是直接展开,在这我顺便说一下宏的好处和坏处。

    宏优点1代码复用性2提高性能

    宏缺点1 不可调试(预编译阶段进行了替换),2无类型安全检查3可读性差,容易出错。

    这里附上《c和指针》中的一张表格,总结宏和函数十分到位,我就不多说了

     

    宏函数很皮,#define定义一个比如判断大小,替换常量,很是方便。

    不过我现在也就用下,#define ERROR_POWEROFF -1,#define _CRT_SECURE_NO_WARNINGS 1这样的和编译器有关的东西,不会去写宏函数,宏函数这东西,可读性特别差,在c++中,一般用const/枚举/内联去替代宏。

    但是,define宏在某些方面真的是非常好用,我很推荐。

    1.替代路径

    #define ENG_PATH_1 C:\Program Files (x86)

    2.针对编译器版本不兼容报错

    #define _CRT_SECURE_NO_WARNINGS 1

    3.条件编译

    #ifdef 标识符
    程序段 1
    #else
    程序段 2
    #endif

    4.使用库中的宏

    vc++中有许多有意思的宏,都是大牛们写出来的,真的是充满智慧,十分刁钻,怎么学也学不完,我个人担心出错就很少写宏,用函数代替了。在以后的博客中我会记录一些常用的,充作笔记。

    emmm,当然,还有其他许多重要的预处理。

    比如

    include

    #include <filename>

    尖括号是预处理到系统规定的路径中去获得这个文件(即 C 编译系统所提供的并存放在指定的子目录下的头文件)。找到文件后,用文件内容替换该语句。如stdio.h

    #include“filename”

    “”则是预处理我们自己第三方的文件,如程序员小刘写的Date.h,我们就可以include“Date.h”

    #error 预处理,#line 预处理,#pragma 预处理

    #error 预处理指令的作用是,编译程序时,只要遇到 #error 就会生成一个编译错误提示消息,并停止编译。

    这个我没写过,但碰到过很多次,在编写mfc代码中,拉入控件时我加入密码框控件,OS编译时会自动弹出#error 提示我该编辑框为密码,注意明文问题

    #line 的作用是改变当前行数和文件名称,如#line 28  liu 

    目前我没使其派上用场,但了解为好。

    #pragma 是比较重要且困难的预处理指令。

    #pragma once 

    这个的做用就是防止头文件多次包含

    当然,还有另外一种风格,防止被包含,我同时给出来

    是巧妙地利用了define宏

    #ifndef _SOME_H
    #define _SOME_H
    
    
    ...//(some.h头文件内容)
    
    
    #endif

    变量的防止重复定义则利用extern,在头文件中不初始化只声明。引用该头文件即可,在链接过程中。就可以使用到这个变量。

    (附:extern在c++中经常用于  extern "C"  告诉编译器下面是c语言风格)

    #pragma warning

    #pragma warning( disable : 4507 34; once : 4385; error : 164 )
    等价于:
    #pragma warning(disable:4507 34) // 不显示 4507 和 34 号警告信息
    #pragma warning(once:4385) // 4385 号警告信息仅报告一次
    #pragma warning(error:164) // 把 164 号警告信息作为一个错误。

    另外还有

    #pragma pack

    使用指令#pragma pack (n),编译器将按照 n 个字节对齐。
    使用指令#pragma pack (),编译器将取消自定义字节对齐方式。
    在#pragma pack (n)和#pragma pack ()之间的代码按 n 个字节对齐。

    字节对齐,我将另起炉灶,在另外一篇博客中归纳总结。

     

    #pragma pack(push) //保存当前对其方式到 packing stack
    #pragma pack(push,n) 等效于
    #pragma pack(push)
    #pragma pack(n) //n=1,2,4,8,16 保存当前对齐方式,设置按 n 字节对齐
    #pragma pack(pop) //packing stack 出栈,并将对其方式设置为出栈的对齐

    #运算符和##预算符

    #define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));

    这段代码中#就是帮助x作为一个变量,表现出来,而不是一个简单的字母

    如果有#,SQR(3)运算出来就是

    The square of 3  is 9

    如果没有# SQL(3)运算出来就是

    The square of x  is 9

    ##预算符

    ##把两个语言符号组合成单个语言符号

    编译

    编译阶段是检查语法,生成汇编,这个属于程序员的必备知识,我们学习一门语言第一步就是知晓语法,其中比较生涩的有左值右值,指针的使用,内存的管理,数据结构的使用,这将会是一场持久战 ,贯穿在整个学习生涯。

    在这里我截取优先级问题,这个可能会通过编译但是不一定达到程序员想要的结果。

    在这里,我引用《c语言深度解剖》中的一张表格

    汇编

      汇编代码转换机器码   这个阶段,非底层的程序员不需要考虑, 编译器不会搞错的。也与c/c++开发者无关,但是我们可以利用反汇编来调试代码,学习汇编语言依然是必备的。

    链接

    开头我引用一下百度百科的介绍

    静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。

    动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。

    将源文件中用到的库函数与汇编生成的目标文件.o合并生成可执行文件。该可执行文件会变大很多,一般是调用自己电脑上的。

    静态库和应用程序编译在一起,在任何情况下都能运行,而动态库是动态链接,文件生效时才会调用。

    很多代码编译通过,链接失败就极有可能在静态库和动态库这出现了纰漏,要视情况解决。缺少相关所需文件,就会链接报错。这个时候就要检查下本地的链接库是不是缺损。

    展开全文
  • 编译原理:总结

    万次阅读 多人点赞 2019-01-08 09:34:59
    编译器概述 编译器的核心功能 编译器的核心功能是把源代码翻译成目标代码: 翻译!!!目标代码!!! 理解源代码:词法分析、语法...转化为等价的目标代码:中间代码生成、目标代码生成 ...语法分析器:单词流-&am

    编译器概述

    编译器的核心功能

    编译器的核心功能是把源代码翻译目标代码

    翻译!!!目标代码!!!

    • 理解源代码:词法分析、语法分析、语义分析
    • 转化为等价的目标代码:中间代码生成、目标代码生成
    • 更好:优化方法

    编译器的结构

    每个阶段将源程序从一种表示转换成另一种表示。
    随着编译器各个阶段的进展,源程序的内部表示不断地发生变化。

    • 词法分析器:字符流->单词流
    • 语法分析器:单词流->语法树
    • 语义分析器:
      • 收集标识符的属性信息:
        • 类型(Type)
        • 种属(Kind)
        • 存储位置、长度
        • 作用域
        • 参数和返回值信息
      • 语义检查:
        • 变量或过程未经声明就使用
        • 变量或过程名重复声明
        • 运算分量类型不匹配
        • 操作符与操作数之间的类型不匹配
    • 中间代码生成器:抽象语法树->中间表示(与平台无关的抽象程序):
      1. 易于产生
      2. 易于翻译成目标程序
      3. 三地址码:temp1=c*d;temp2=b+temp1;a=temp2
      4. 四元式:(op, arg1, arg2, result);(* , c , d , temp1);(+ , b, temp1 , temp2);(= , temp2 , - , a)
    • 代码优化器:试图改进中间代码,以产生执行速度较快的机器代码:
      • temp1=c*d;temp2=b+temp1;a=temp2
      • change to:temp1=c*d;a=b+temp1
    • 代码生成器:生成可重定位的机器代码或汇编代码:
      • temp1=c*d;a=b+temp1
      • change to:Mov R2,c;Mul R2, d;Mov R1, b;Add R2, R1;Mov a, R2
      • 一个重要任务是为程序中使用的变量合理分配寄存器
    • 符号管理表:
      • 基本功能是记录源程序中使用的标识符,
      • 并收集与每个标识符相关的各种属性信息,
      • 并将它们记载到符号表中。
    • 错误处理器:
      • 处理方式:报告错误,应继续编译
      • 大部分错误在语法分析、语义分析阶段检测出来
      • 词法分析:字符无法构成合法单词
      • 语法分析:单词流违反语法结构规则
      • 语义分析:语法结构正确,但无实际意义

    在这里插入图片描述


    程序设计语言及其文法

    字母表和串上的运算

    串:是字母表中符号的一个有穷序列。
    ∑ 是一个字母表,任意 x∈∑*,x 是 ∑ 上的一个串。

    语言的定义和运算、句子的定义

    设 ∑ 是一个字母表,任意L 属于 ∑*,L 称为字母表 ∑ 上的一个语言
    任意 x∈L,x 叫做 L 的一个句子

    文法的定义

    文法是用于描述语言的语法结构的形式规则。
    任何一种语言都有它自己的文法,不管它是机器语言还是自然语言。

    • 自然语言的文法:主 谓 宾
    • 机器语言也有描述它语言构成的特定文法

    文法可以定义为一个四元组(VT, VN, S, P)

    1. 一个终结符号集合VT
    2. 一个非终结符号集合VN
    3. 一个产生式集合P,定义语法范畴。产生式:A → α,产生式:描述了将终结符和非终结符组合成串的方法
    4. 一个特定的非终结符——开始符号S

    文法的四种类型

    • 0型文法:也称为短语文法
      • ∀α → β∈P,α中至少包含1个非终结符
      • 任何0型语言都是递归可枚举的;反之,递归可枚举集必定是一个0型语言。
    • 1型文法:上下文有关文法context-sensitive
      • ∀ α→β,都有|α| ≤ |β| ,仅 S→ε除外
      • 产生式的形式描述:α1Aα2 → α1βα2 (α1, α2,β∈(VN∪VT ) *,β≠ε,A∈ VN)
      • 即:A只有出现在α1, α2的上下文中,才允许用β替换。
    • 2型文法:上下文无关文法, context-free grammar. CFG
      • ∀ α→β,都有α∈VN,β∈(VN∪VT)*
      • 产生式的形式描述:A →β (A∈VN)
      • 即:β取代A时,与A所处的上下文无关。
    • 3型文法:regular grammar, RG,也称正则文法
      • 每个产生式均为 “A→aB”或“A→a” —— 右线性
      • 或 “A→Ba”或“A→a” —— 左线性
      • (A、B∈VN,a∈VT*)

    推导和归约

    推导:用产生式的右部替换产生式的左部

    句子、句型和语言的定义

    • 如果Sαα(VTVN)S →^* α,α∈(V_T∪V_N)^*,则称α是G的一个句型
    • 如果SwwVTS →^* w,w ∈V_T^*,则称w是G的一个句子
    • 由文法G的开始符号S推导出的所有句子构成的集合称为文法G生成的语言,记为L(G)。即L(G)={w|SwwVTS →^* w,w ∈V_T^*}

    语法生成树

    用图形的方式展示推导过程

    二义性:多个语法分析树生成同一个终结符号串

    程序语言定义

    任何语言实现的基础是语言定义
    语言的定义决定了该语言具有什么样的语言功能、 什么样的程序结构、以及具体的使用形式等细节问题。

    • 词法:是指单词符号的形成规则。(状态转换图、正则表达式和有穷自动机)
    • 语法:是指一组规则,用它可产生一个程序。(下推自动机理论和上下文无关文法是讨论语法分析的理论基础)
    • 规则:词法规则 + 语法规则
    • 语义:定义语言的单词符号和语法单位的意义。(属性方法和基于属性文法的语法制导翻译方法)
    • 形式语言:上述的定义是用文字来描述的,当设计编译程序时,就要把它用形式的方式描述出来,就要用到形式语言。

    参数传递方法:

    1. 传值调用
    2. 引用调用
    3. 复制恢复(传值结果)
    4. 传名调用

    词法分析

    词法分析器的作用

    • 识别源程序中的单词是否有误
    • 读入源程序字符流、组成词素,输出词法单元序列
    • 过滤空白、换行、制表符、注释等
    • 将词素添加到符号表中

    词法分析器的角色

    编译过程的分析部分:词法分析 + 语法分析

    • 简化编译器的设计:词法分析器可以首先完成一些简单的处理工作
    • 提高编译器的效率:相对于语法分析,词法分析过程简单,可高效实现增强编译器的可移植性(输入设备无关)

    输入缓冲

    三种实现方式

    1. 自动生成工具——Lex,生成工具提供读取输入和缓冲的函数
    2. 高级语言手工编码,利用高级语言提供的I/O函数
    3. 汇编语言编程,直接访问磁盘
    • 1 → 3,性能↗ ,实现难度↗
    • 唯一读取文件的阶段,值得优化
    • 方案:
      • 双缓冲方案
      • 哨兵标记

    词法单元的描述:正则表达式定义规则

    若 r, s为正则表达式,表示语言L(r)L(s)L(r)和L(s),则(从1到4优先级递增)

    1. (r)(s)L(r)L(s)(r) | (s)是正则表达式,表示语言L(r)∪L(s)
    2. (r)(s)L(r)L(s)(r)(s)是正则表达式,表示语言L(r)L(s)
    3. (r)(L(r))(r)*是正则表达式,表示语言(L(r))*
    4. (r)L(r)(r) 是正则表达式,表示语言L(r)

    正则表达式等价(equivalent):r = s → 表示的语言相同,L(r)=L(s)L(r) = L(s)

    • 能被5整除的10进制整数:[1-9][0-9]*(0|5)|0|5(!!!
    • C 语言标识符:[_a-zA-Z][_a-zA-Z0-9]*
    • C 语言无符号数的集合:
      • digit → [0-9]
      • digits → digit+
      • number → digits (.digits)? (E(+|-)? digits)?
    • 不包含连续的0的01串:((ε|0)1)*0?

    词法单元的识别

    状态转换图(注意初态和终态)

    不确定有限自动机

    数学模型,表示为五元组 M = {S,Σ,δ,s0,F},其中

    • S:有限状态集
    • Σ:有穷字母表,其元素为输入符号
    • δ:S×Σ到S的子集的映射,即 δ: S×(Σ∪{ε}) → 2S2^S,状态转换函数。
    • s0∈S 是唯一的初态
    • F⊆S 是一个终态集

    正则表达式(描述单词)
    NFA(定义语言):存在缺点,同一符号或ε和其它符号 → 多义性,难以实现
    DFA(适于计算机实现)

    NFA 和 DFA 都可识别正则表达式,时-空折衷
    词法分析器可用一组NFA来描述,每个NFA表示一个单词

    设计自动机(注意初态的 →start 和终态的双圈)

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    词法分析器的构造

    正则表达式 → 构造NFA(Thompson构造法)

    构造规则:注意开始的箭头和结束的双圈

    • N(ε)

    在这里插入图片描述

    • N(a)

    在这里插入图片描述

    • N(s|t)

    在这里插入图片描述

    • N(st)

    在这里插入图片描述

    • N(s*)

    在这里插入图片描述

    NFA → 转换DFA(子集构造法)

    在这里插入图片描述

    1. ε-closure(0)={0,1,2,4,7}=A
    2. ε-closure(δ(A,a)) = ε-closure(δ({0,1,2,4,7},a))
      = ε-closure({3,8})
      = {1,2,3,4,6,7,8} = B
      ∴ Dtran[A,a] = B
      ε-closure(δ(A,b)) = ε-closure(δ({0,1,2,4,7},b))
      = ε-closure({5}) = {1,2,4,5,6,7} = C
      ∴ Dtran[A,b] = C
    3. ε-closure(δ(B,a)) = ε-closure(δ({1,2,3,4,6,7,8},a))
      = ε-closure({3,8})
      = {1,2,3,4,6,7,8} = B
      ∴ Dtran[B,a] = B
      ε-closure(δ(B,b)) = ε-closure(δ({1,2,3,4,6,7,8},b))
      = ε-closure({5,9})
      = {1,2,4,5,6,7,9} = D
      ∴ Dtran[B,b] = D
    4. ε-closure(δ(C,a)) = ε-closure(δ({1,2,4,5,6,7},a))}
      = ε-closure({3,8})
      = {1,2,3,4,6,7,8} = B
      ∴ Dtran[C,a] = B
      ε-closure(δ(C,b)) = ε-closure(δ({1,2,4,5,6,7},b))
      = ε-closure({5})
      = {1,2,4,5,6,7} = C
      ∴ Dtran[C,b] = C
    5. ε-closure(δ(D,a)) = ε-closure(δ({1,2,4,5,6,7,9},a))}
      = {1,2,3,4,6,7,8} = B
      ∴ Dtran[D,a] = B
      ε-closure(δ(D,b)) = ε-closure(δ({1,2,4,5,6,7,9},b))}
      = {1,2,4,5,6,7,10} = E
      ∴ Dtran[D,b] = E
    6. ε-closure(δ(E,a)) = ε-closure(δ({1,2,4,5,6,7,10},a))}
      = {1,2,3,4,6,7,8} = B
      ∴ Dtran[E,a] = B
      ε-closure(δ(E,b)) = ε-closure(δ({1,2,4,5,6,7,10},b))}
      = {1,2,4,5,6,7} = C
      ∴ Dtran[E,b] = C
    7. 最终结果:

    在这里插入图片描述

    最小化DFA状态

    • 初始,两个组:终态与非终态
    • 划分方法,对于状态组A={s1,s2,…,sk}
      • 对符号a,得到其转换状态t1,t2,…,tk
      • 若t1,t2,…,tk属于不同状态组,则需将A对应划分为若干组

    在这里插入图片描述

    在这里插入图片描述


    语法分析

    语法分析器的类型

    在自顶向上的语法分析方法中,分析的关键是:选择候选式
    在自底向上的语法分析方法中,分析的关键是:寻找句柄

    自顶向下分析器:递归下降法,LL(1)
    自底向上分析器:LR()

    LL 和 LR 都不能表示所有 CFG。

    LR分析表种类:

    • LR(0):局限性大,但它是建立其它分析表的基础
    • SLR (1) :易实现,功能比LR(0)稍强些
    • LR(1) :分析能力最强,但实现代价高
    • LALR (1) :功能介于SLR(1)和LR(1)之间,适用于大多数程序设计语言的结构,并且可以比较有效地实现。同心集。

    语法分析器的作用

    • 利用语法检查单词流的语法结构
    • 构造语法分析树
    • 语法错误和修正
    • 识别正确语法
    • 报告错误

    语法错误处理

    不同层次的错误

    • 词法:拼写错误(then → them)
    • 语法:单词漏掉、顺序错误(花括号不配对)
    • 语义:类型错误(声明void f()和调用aa = f())
    • 逻辑:无限循环/递归调用( == → = )

    错误处理目标

    • 清楚、准确地检测、报告错误及其发生位置
    • 快速恢复,继续编译,以便发现后续错误
    • 不能对正确程序的编译速度造成很大影响

    LL,LR,可最快速度发现错误:可行前缀特性,viable-prefix property,当一个输入前缀不是语言中任何符号串前缀——发生错误。

    错误恢复策略

    1. 错误恢复策略
      • 丢弃单词,直到发现 “同步”单词,设计者指定同步单词集,{end, “;”, “}”, …}
      • 缺点:丢弃输入导致遗漏定义,造成更多错误;遗漏错误
      • 优点:简单 → 适合每个语句一个错误的情况
    2. 短语层次的恢复
      • 局部修正,继续分析,同样由设计者指定修正方法。e.g. “,” → “;”,删除“,”,插入“;”
      • 与恐慌模式相结合,避免丢弃过多单词
    3. 错误产生式
      • 理解、描述错误模式,文法添加生成错误语句的产生式,拓广文法 → 语法分析器程序。( 错误检测信息+自动修正)
      • e.g. 对C语言赋值语句,为“:=”添加规则 报告错误,但继续编译
    4. 全局纠正
      • 错误程序 → 正确程序,寻找最少修正步骤,插入、删除、替换
      • 过于复杂,时空效率低

    上下文无关文法

    推导:描述文法定义语言的过程,自顶向下构造语法分析树的精确描述。

    两个CFG生成相同语言,两个CFG等价。

    最左推导,最右推导。

    语法树:推导的图示,但不体现推导过程的顺序。

    一棵语法树对应多个推导,但是有唯一的最左推导和最右推导。

    二义性文法存在某个句子对应多个语法树,多个最左(右)推导。

    证明CFG G生成语言L:(数学归纳法)

    1. G生成的每个符号串都在L中
    2. L中每个符号串都可由G生成

    正则表达式可描述的语言CFG均可描述。
    RG 都可用 NFA 表示,CFG 都是 RG,所以 CFG 都可用 NFA 表示。

    为什么还需要正则表达式?

    • 词法规则很简单,正则表达式描述能力足够
    • 正则表达式更简洁、更容易理解
    • 能更自动构造更高效的词法分析器
    • 使编译器前端更模块化

    设计 CFG

    在这里插入图片描述

    CFG 的修改

    • 消除二义性
    • 消除左递归(间接左递归):见附录
    • 消除空产生式:利用产生式进行代入
    • 消除回路:保证每个产生式都加入终结符(开始符号的空产生式除外)
    • 提取左公因子:预测分析方法要求—— 向前搜索一个单词,即可确定产生式

    在这里插入图片描述

    在这里插入图片描述

    CFG 优点

    • 给出精确的,易于理解的语法说明
    • 自动产生高效的分析器
    • 可以给语言定义出层次结构
    • 以文法为基础的语言的实现便于语言的修改

    问题:CFG 文法只能描述编程语言的大部分语法。(无法表示标识符必须在使用前定义,函数形参和实参数目应匹配)

    递归下降分析法

    缺点

    • 不能处理左递归
    • 复杂的回溯技术
    • 回溯导致语义工作推倒重来
    • 难以报告出错的确切位置
    • 效率低

    产生回溯的原因:进行推导时,若产生式存在多个候选式,选择哪个候选式进行推导存在不确定性。
    消除回溯的基本原则:对文法的任何非终结符,若能根据当前读头下的符号,准确的选择一个候选式进行推导,那么回溯就可以消除。

    LL(1):构造预测分析表

    计算FIRST和FOLLOW函数

    • FIRST

    FIRST(X1 X2 … Xn ) = FIRST (X1) “+”
    FIRST(X2) if ε is in FIRST(X1) “+”
    FIRST(X3) if ε is in FIRST(X2) “+”

    FIRST(Xn) if ε is in FIRST(Xn-1)
    注意:仅当对所有i,e∈FIRST(Xi),才将e加入FIRST(X1 X2 … Xn)

    • FOLLOW

    $加入FOLLOW(S),S为开始符号,$为输入串结束标记
    若A → αBβ,则FIRST(β)中符号除ε外,均加入FOLLOW(B)
    若A → αB 或 A → αBβ 且 β →* ε,FOLLOW(A)中所有符号加入FOLLOW(B)

    在这里插入图片描述

    应用构造算法

    • 对所有的终结符a ∈ FIRST(α),将 A->α 加入M[A,a]
    • 若ε ∈ FIRST(α),对所有的终结符b ∈ FOLLOW(A),将 A->α 加入M[A,b]
    • 所有未定义的表项设置为错误

    在这里插入图片描述

    e.g. id+id*id

    在这里插入图片描述

    LL(1)文法

    若文法G的预测分析表M中不含有多重定义项,则称G为 LL(1)文法。且无二义性,无左递归。

    计算 First 和 Follow 习题

    在这里插入图片描述

    预测分析法的错误恢复

    恐慌模式恢复策略

    考虑非终结符的同步单词集

    • FOLLOW(A)——略过A
    • 将高层结构的开始符号作为低层结构的同步集
    • FIRST(A)——重新开始分析A

    在这里插入图片描述

    • Follow 集为 sync,弹出一个非终结符
    • 其他的空位则跳过输入符号

    在这里插入图片描述

    短语层次错误恢复

    预测分析表空位填入错误处理函数

    自顶向下分析:预测分析法

    预测分析法实现步骤

    • 构造文法
    • 改造文法:消除二义性、消除左递归、消除回溯
    • 求每个变量的FIRST集和FOLLOW集,构造预测分析表
    • 检查是不是LL(1) 文法
    • 对于递归的预测分析,为每一个非终结符编写一个过程;对于非递归的预测分析,实现表驱动的预测分析算法

    缺点:不是所有文法满足LL(1)要求

    自底向上分析方法

    自底向上语法分析,是从输入符号串出发,试图把它归约成识别符号。
    从图形上看,自底向上分析过程是以输入符号串作为末端结点符号串,向着根结点方向往上构造语法树,使识别符号正是该语法树的根结点。

    归约:某产生式体相匹配的特定子串被替换为该产生式头部的非终结符号

    自底向上分析是一个不断进行直接归约的过程。任何自底向上分析方法的关键是要找出这种句柄。

    符号串的句柄是与某个产生式右部匹配的子串,应该归约为产生式左部(最右推导的逆过程)。

    一个句型可有多个不同句柄,但是非多义性文法的最右句型有唯一句柄。

    基本操作:移进、归约、接受、错误。

    若B为非终结符,则 A→a · Bb 为待约项目。

    LR分析方法:当前最广义的无回溯的“移进- 归约”方法。

    优点:适用范围广;分析速度快;报错准确。

    从逻辑上说,一个LR分析器包括两部分:一个总控程序和一张分析表。

    构造 LR(0)分析表

    1. 写出给定文法G的增广文法G’并编号
    2. 构造识别可行前缀的DFA
    3. 根据DFA构造LR(0)分析表

    LR(0)示例

    • 示例文法

    在这里插入图片描述

    • 示例文法的分析表

    在这里插入图片描述

    构造 SLR(1)分析表

    解决同一项目集中的移进-归约冲突。

    对于冲突项目集 Ii ={A→β1·bβ2,B→β·,C→β·},如果集合FOLLOW(B)和FOLLOW(C)不相交,而且不包含b,那么,当状态Ii面临任何输入符号a时,可采用如下“移进—归约”的决策。

    ①当a=b时,则移进,置ACTION[i,a]=Sj
    ②当a∈FOLLOW(B)时,置ACTION [i,a]=rj
    ③当a∈FOLLOW(C)时,置ACTION [i,a]=rm
    ④当a不属于三种情况之一,置ACTION [i,a]=“ERROR”

    一般地,若一个项目集 Ii 含有多个移进项目和归约项目,例如
    Ii={A1→α·a1β1,A2→α·a2β2,…,Am→α·amβm,B1→α·,B2→α·,…,Bn→α·}
    如果集合{a1,a2,…,am},FOLLOW(B1),FOLLOW(B2),… FOLLOW(Bn)两两不相交时,可类似地根据不同的当前符号,对状态为i中的冲突动作进行区分。这种解决“移进—归约”冲突的方法称作SLR方法。

    SLR(1)文法:对于给定的文法G,若按上述方法构造的分析表不含多重定义的元素,则称文法G是SLR(1)文法。

    SLR(1)示例

    设有文法G
    E→E+T|T
    T→T*F|F
    F→(E)|id
    构造该文法SLR(1)分析表。

    ① 将文法G增广为G′,同时对每一产生式进行编号
    (0)S′→E
    (1)E→E+T
    (2)E→T
    (3)T→T*F
    (4)T→F
    (5)F→(E)
    (6)F→id
    ②对G′构造文法LR(0)项目集规范族如下:

    在这里插入图片描述

    ③ 取这些项目集作为各状态,并根据转换函数GO画出识别文法G′的有穷自动机,

    在这里插入图片描述

    ④ 用SLR方法解决“移进—归约”冲突。
    在十二个项目集中, I1、 I2 和 I9 都含有“移进—归约”冲突,其解决办法是:
    对于项目集 I1 ={S′→E·,E →E·+T},由于集合 FOLLOW(S′)={$}与集合{+}不相交,所以当状态为1时,面临着输入符号为+时便移进,而面临着输入符号为$时,则按产生式S′→E归约。对于项目集 I2 ={E→T·,T→T·*F},由于集合 FOLLOW(E)={+,),$}与集合{*}不相交,因此状态2面临输入符号为*时移进,而面临输入符号为+或)或$时,按产生式E→T归约。对于项目集I9 ={E →E+T·,T →T·*F},同样由于 FOLLOW(E) = { +, ), $ }与集合{*}不相交,因此状态9面临着输入符号为*时移进,面临着输入符号为+或)或$ 时,按产生式E→E+T归约。

    ⑤ 构造SLR(1)分析表

    在这里插入图片描述

    • 输入串为id+id*id为例,给出LR分析器的分析过程如下表:
    步骤 状态栈 符号栈 输入串 分析动作 下一状态
    1 0 id+id*id$ S5 5
    2 05 $id +id*id$ r6 GOTO[0,F]=3
    3 03 $F +id*id$ r4 GOTO[0,T]=2
    4 02 $T +id*id$ r2 GOTO[0,E]=1
    5 01 $E +id*id$ S6 6
    6 016 $E+ id*id$ S5 5
    7 0165 $E+id *id$ r6 GOTO[6,F]=3
    8 0163 $E+F *id$ r4 GOTO[6,T]=9
    9 0169 $E+T *id$ S7 7
    10 01697 $E+T* id$ S5 5
    11 016975 $E+T*id r6 GOTO[7,F]=10
    12 0169710 $E+T*F r3 GOTO[6,T]=9
    13 0169 $E+T r1 GOTO[0,E]=1
    14 01 $E acc

    LR(1)

    SLR(1)也存在不足,即如果冲突项目的非终结符FOLLOW集与有关集合相交时,就不能用SLR(1)方法解决。

    SLR(1)是 LR(1)的一种特例,即所有 SLR(1)文法都是 LR(1)文法。

    SLR只是简单地考察下一个输入符号b是否属于与归约项目A→α相关联的FOLLOW(A),但b∈FOLLOW(A)只是归约α的一个必要条件,而非充分条件。

    对于产生式A→α的归约,在不同的使用位置,A会要求不同的后继符号。在特定位置,A的后继符集合是FOLLOW(A)的子集。

    **任何二义性文法都不是LR(k)文法。 **

    LALR

    LALR分析法与SLR相类似,但功能比SLR(1)强,比LR(1)弱。

    首先构造LR(1)项目集,如果它不存在冲突,就把同心集合并在一起,若合并后项目集规范族不存在“归约—归约”冲突,就按照这个集族构造分析表。``

    一个LR(1)文法合并同心集后若不是LALR(1)文法,则可能存在归约/归约冲突,不可能存在移进/归约冲突。


    语法制导翻译

    语法制导翻译概述

    语法制导翻译使用CFG来引导对语言的翻译,是一种面向文法的翻译技术

    在这里插入图片描述

    语义分析器的任务:
    ① 检查各个语法结构的静态语义 (静态语义分析或静态检查 )
    ② 执行所规定的语义动作:如表达式的求值、符号表的填写、中间代码的生成
    将静态检查和中间代码生成结合到语法分析中进行的技术称为语法制导翻译

    语法制导的翻译:一种形式化的语义描述方法,包括两种具体形式:

    • 语法制导定义(Syntax-Directed Definitions, SDD):定义翻译所必须的语义属性和语义规则,一般不涉及计算顺序。
    • 翻译模式(translation schemes): 给出语义规则的计算顺序。

    语法制导定义(SDD)

    • 一个上下文无关文法。
    • 每个属性与文法的一个终结符或非终结符相关联
    • 每一个产生式和一个语义规则集合相关联。描述产生式中各文法符号的属性之间的依赖关系。通常用函数或程序语句的形式表示

    语法制导定义是一种接近形式化的语义描述方法。语法制导定义的表示分两部分:

    • 先针对语义为文法符号设置属性,
    • 然后为每个产生式设置语义规则,来描述各属性间的关系。

    继承属性和综合属性

    属性的特点:

    • 一个结点的综合属性的值通过分析树中它的子结点的属性值和自己的属性值计算的;
    • 继承属性的值由结点的父结点、兄弟结点来计算的;
    • 非终结符号即可有综合属性也可有继承属性,但文法开始符号没有继承属性;
    • 终结符号只有综合属性,由词法分析器提供,即记号的属性;
    • 每个文法符号的综合属性和继承属性的交集为空。

    判断一个属性是继承属性还是综合属性

    综合属性(synthesized attribute):

    • 在分析树结点 N 上的非终结符 A 的综合属性只能通过 N 的子结点N 本身的属性值来定义。
    • 终结符可以具有综合属性。终结符的综合属性值是由词法分析器提供的词法值,因此在SDD中没有计算终结符属性值的语义规则。

    继承属性(inherited attribute)

    • 在分析树结点 N 上的非终结符 A 的继承属性只能通过 N 的父结点、N 的兄弟结点或 N 本身的属性值来定义。
    • 终结符没有继承属性。终结符从词法分析器处获得 的属性值被归为综合属性值。

    继承属性和综合属性的计算在产生式中所在的位置

    • 将计算某个非终结符号A的继承属性的动作插入到产生式右部中紧靠在A的本次出现之前的位置上
    • 将计算一个产生式左部符号的综合属性的动作放置在这个产生式右部的最右端

    副作用

    副作用:一般属性值计算(基于属性值或常量进行的)之外的功能,如,打印出一个结果;将信息加入符号表。

    注释分析树

    注释分析树:将属性附着在分析树对应文法符号上(属性作为分析树的注释)。

    SDD的求值顺序

    • 对单词符号串进行语法分析,构造语法分析树
    • 根据需要构造属性依赖图;(综合属性在右,继承属性在左)
    • 遍历语法树,并在语法树各结点处按语义规则进行计算

    在这里插入图片描述

    S属性与L属性

    仅仅使用综合属性的 SDD 称为 S 属性的 SDD

    如果一个SDD是S属性的,可以按照语法分析树节点的任何自底向上顺序来计算它的各个属性值。

    L-属性定义的直观含义:在一个产生式所关联的各属性之间,依赖图的边可以从左到右,但不能从右到左。即 A->X1X2……Xn 中 Xi 的每个继承属性仅依赖于:

    • A 的继承属性
    • Xi 左边的符号X1,……Xi-1 的属性
    • Xi 本身的属性,但是不能在依赖图中形成环路。

    每个S-属性定义都是L-属性定义
    把L-属性定义变换成翻译模式,在构造翻译程序的过程中前进了一大步。

    语法制导的翻译方案

    示例1:实现桌上计算器的后缀 SDT

    val 为综合属性,计算其值都在产生式的末尾。

    L → En { print (E.val); }
    E → E1 + T { E.val = E1 .val + T.val;}
    E → T { E.val = T.val;}
    T → T1 * F { T.val = T1.val * F.val ;}
    T → F { T.val = F.val; }
    F → (E) { F.val = E.val; }
    F → digit { F.val = digit.lexval; }

    示例2:给出下列文法的 SDT

    为文法
    S → ( L ) | a
    L → L , S | S
    写一个翻译方案,它输出每个a的嵌套深度。例如,对于( a , ( a , a) ),输出的结果是1 2 2。

    depth 为继承属性,计算其值在紧靠非终结符出现之前。

    S’→ {S. depth = 0 } S
    S → {L. depth = S. depth + 1 } ( L )
    S → a {print (S. depth) }
    L → {L1. depth = L. depth }L1 , {S. depth = L. depth }S
    L → {S. depth = L. depth }S

    示例3:根据下列文法的 SDT

    有文法G及其语法制导翻译如下所示:(语义规则中的*和+为运算符乘号和加号)
    E → E1^T{E.val=E1.val * T.val}
    E → T{E.val= T.val}
    T → T1#n{T.val=T1.val +n.val}
    T → n{T.val= n.val}
    采用自底向下分析时,句子3∧3#4其值为:21

    示例4:给出下列文法的 SDT

    为文法
    S → ( L ) | a
    L → L , S | S
    写一个翻译方案,它打印出每个a在句子中是第几个字符。例如,当句子是( a , ( a , ( a , a ) , (a) ) )时,打印的结果是2 5 8 10 14。

    in 为继承属性,out 为综合属性。

    每个非终结符左侧计算其 in 属性,每个产生式最右侧计算其 out 属性。

    S’ → {S. in = 0 } S
    S → {L. in = S. in + 1 } ( L )
    {S. out = L. out + 1 }
    S → a {S. out = S. in + 1; print (S. out) }
    L → {L1. in = L. in }L1 ,
    {S. in = L1. out + 1 } S
    {L. out = S. out }
    L → {S. in = L. in }S {L. out = S. out }

    示例5:给出下列文法的 SDT

    为文法
    S → ( L ) | a
    L → L , S | S
    写一个翻译方案,它输出括号的对数。

    num 为综合属性。

    S’ → S {print (S. num)}
    S → ( L ) { S. num = L.num + 1}
    S → a {S. num = 0}
    L → L1 , S { L. num = L1. num + S. num}
    L → S { L. num = S.num }


    中间代码生成

    中间代码生成概述

    “中间代码生成”程序的任务是:把经过语法分析和语义分析而获得的源程序中间表示翻译为中间代码表示。

    方法:语法制导翻译。

    采用独立于机器的中间代码的好处:

    1. 便于编译系统建立和编译系统的移植;
    2. 便于进行独立于机器的代码优化工作。

    中间表示

    优点: 容易为不同目标机器开发不同后端
    缺点: 编译过程变慢 (因为中间步骤)

    中间表示:

    • 语法树:抽象语法树
      • 反映了抽象的语法结构,而分析树反映的是具体的语法结构。语法树是分析树的抽象形式或压缩形式。
      • 语法规则中包含的某些符号可能起标点符号作用,也可能起解释作用
    • 三地址代码表示
      • 四元式
      • 三元式
      • 间接三元式

    语法分析树,抽象语法树,有向无环图(DAG)

    抽象语法树(或者简称为语法树)反映了抽象的语法结构,而语法分析树(分析树)反映的是具体的语法结构。语法树是分析树的抽象形式或压缩形式。

    • ((a)+(b))的语法分析树和抽象语法树

    在这里插入图片描述

    • 有向无环图

    在这里插入图片描述

    三地址码的实现

    四元式 op, arg1, arg2, result

    在这里插入图片描述

    三元式 op, arg1, arg2

    三元式可以避免引入临时变量,使用获得变量值的位置来引用前面的运算结果。

    在这里插入图片描述

    间接三元式 间接码表+三元式表

    包含一个指向三元式的指针列表,而不是列出三元式序列本身。

    在这里插入图片描述

    类型和声明

    类型检查:利用一组逻辑规则来确定程序在运行时的行为,保证运算分量的类型和运算符的预期类型匹配。

    翻译时的应用:

    • 确定一个名字需要的存储空间
    • 计算一个数组元素引用的地址
    • 插入显式的类型转换
    • 选择算术运算符的正确版本

    类型构造符

    • 数组
    • 笛卡尔积
    • 记录
    • 指针
    • 函数

    类型表达式示例

    int[2][3] array(2,array(3,integer))
    int *f(char a, char b) (char×char) → pointer(integer)
    typedef struct person={
    char name[8];
    int sex;
    int age;
    }
    struct person table[50];
    person record( (name × array(8,char)) × (sex × integer) × (age × integer))
    table array(50,person)

    类型等价

    类型等价:结构等价 + 名等价
    结构等价:满足以下条件之一:(类型名被类型表达式所代替,if 替换所有名字后,两个类型表达式结构上等价即结构等价)

    • 相同的基本类型
    • 将相同类型构造算子应用于等价的类型而构建的
    • 一个类型是另一个类型表达式的名字
      名等价:满足前两个条件(将每个类型名看作是可区分的类型,名字完全相同即名等价)

    在这里插入图片描述

    计算类型及其宽度的语法制导翻译

    T → B { t = B.type; w = B.width;}
    C { T.type = C.type; T.width = C.width;}
    B → int { B.type = integer; B.width = 4;}
    B → float { B.type = float; B.width = 8;}
    C → ε { C.type = t; C.width = w;}
    C → [num] C1 { C.type = array( num.vale, C1.type);
    C.width = num.value × C1.width;}

    习题

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述


    运行存储分配

    运行存储分配概述

    运行时刻环境

    • 为源程序中命名的对象分配安排存储位置
    • 确定目标程序访问变量时使用的机制
    • 过程之间的连接
    • 参数传递

    主题

    • 存储管理:栈分配、堆管理、垃圾回收
    • 对变量、数据的访问

    静态和动态存储

    • 静态分配

      • 编译器在编译时刻就可以做出存储分配决定,不需要考虑程序运行时刻的情形
      • 全局变量
      • 静态分配给语言带来限制
        • 递归过程不被允许
        • 数据对象的长度和它在内存中位置的限制,必须是在编译时可以知道的
        • 数据结构不能动态建立
    • 动态分配

      • 栈式存储:和过程的调用/返回同步进行分配和回收,值的生命期和过程生命期相同
      • 堆存储:数据对象比创建它的过程调用更长寿

    堆空间:存储管理器

    堆空间,用于存放生命周期不确定、或生存到被明确删除为止的数据对象。

    存储管理器基本功能

    • 分配:为每个内存请求分配一段连续的、适当大小的堆空间。
      • 首先从空闲的堆空间分配;
      • 如果不行则从操作系统中获取内存、增加堆空间。
    • 回收:把被回收的空间返回空闲空间缓冲池,以满足其他内存需求。

    评价存储管理器的特性:

    • 空间效率:使程序需要的堆空间最小,即减小碎片
    • 程序效率:充分运用内存系统的层次,提高效率
    • 低开销:使分配/收回内存的操作尽可能高效

    两大问题:

    • 内存泄露:未能删除不可能再被引用的数据
    • 悬空指针引用:引用已被删除的数据

    其他问题

    • 空指针访问/数组越界访问

    代码生成

    代码生成器概述

    • 根据中间表示生成代码
    • 代码生成器之前可能有一个优化组件
    • 代码生成器的三个任务
      • 指令选择:选择适当的指令实现IR语句
      • 寄存器分配和指派:把哪个值放在哪个寄存器中
      • 指令排序:按照什么顺序安排指令执行

    静态/栈式数据区分配

    活动记录静态分配:每个过程静态地分配一个数据区域,开始位置用staticArea表示。

    活动记录栈式分配:

    • 寄存器SP指向栈顶
    • 第一个过程(main)初始化栈区
    • 过程调用指令序列
    • 返回指令序列

    基本块相关的代码生成

    中间代码的流图表示法

    • 中间代码划分成为基本块(basic block),其特点是单入口单出口,即:
      • 控制流只能从第一个指令进入
      • 除了基本块最后一个指令,控制流不会跳转/停机
    • 流图中结点是基本块,边指明了哪些基本块可以跟在一个基本块之后运行

    流图可作为优化的基础

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

    划分基本块的算法

    确定首指令(基本块的第一个指令)符合以下任一条:

    • 中间代码的第一个三地址指令
    • 任意一个条件或无条件转移指令的目标指令
    • 紧跟在一个条件/无条件转移指令之后的指令

    确定基本块

    • 每个首指令对应于一个基本块:从首指令(包含)开始到下一个首指令(不含)

    基本块划分示例

    在这里插入图片描述

    窥孔优化

    • 消除冗余指令
    • 消除不可达指令
    • 控制流优化
    • 代数化简/强度消减
    • 机器特有指令的使用

    简单的代码生成算法

    重要子函数:getReg(I)

    • 为三地址指令I选择寄存器;
    • 根据寄存器描述符和地址描述符、以及数据流信息来分配最佳的寄存器;
    • 得到的机器指令的质量依赖于getReg函数选取寄存器的算法

    代码生成时必须更新寄存器描述符和地址描述符。


    代码优化

    代码优化概述

    通过一些相对低层的语义等价转换来优化代码。

    • 公共子表达式消除
    • 复制传播
    • 死代码消除
    • 代码移动
    • 归纳变量和强度消减
    • 常量折叠

    附录

    作业一

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    作业二

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • 编译的几个过程

    2019-09-04 08:47:19
    编译的几个过程 我们经常会使用很多的继承开发环境,也有时候称为是编译器,但是我们经常听到的编译实际上包含很多的步骤,大致可以分为以下几个过程:预处理、编译、汇编、链接。下面我们以c++的编译过程作为例子...

    编译的几个过程

    我们经常会使用很多的继承开发环境,也有时候称为是编译器,但是我们经常听到的编译实际上包含很多的步骤,大致可以分为以下几个过程:预处理、编译、汇编、链接。下面我们以c++的编译过程作为例子进行解释。

    1、预处理

    预处理的过程简单的来说就是对所有的预处理命令进行简单的程序上的叠加,比如有**#include头文件、#define宏定义**,编译器在进行编译时首先会把该这些内容简单的叠加至所需编译的程序的开头,同时做出以下几个处理:

    1. 删除注释 ,我们知道注释不参与编译的过程,在这一步就已经将其删掉;
    2. 展开宏定义#define
    3. 展开头文件#include
    4. 添加行号,以及所需要的其他的标识符

    demo:

    1.首先创建一个c++源文件,这里我们使用记事本创建helloWorld.cpp,源代码如下:

    #include <iostream>
    using namespace std;
    int main()
    {
        cout<<"HelloWorld"<<endl;
        return 0;
    }
    

    我们使用的是MinGw编译器,进行预处理:
    输入命令行:g++ -E helloWorld.cpp -o helloWorld.i ,这意味着在输入命令后会生成 .i 文件,如下:
    在这里插入图片描述
    以及生成.i 文件:
    此时生成helloWorld.i文件
    至于生成的.i 文件内容,我们就不打开仔细分析了,因为是不太好理解。

    2、编译

    编译就是成成汇编语言的过程,编译后的文件保存为.s 文件,这个文件我们可以打开看一下,应该是可以看懂的,具体的操作如下:
    首先输入命令行:g++ -S helloWorld.i -o helloWorld.s ,这意味着在输入命令后会生成 .s 文件,如下:
    在这里插入图片描述
    以及生成.s 文件:
    在这里插入图片描述
    现在我们打开.s 文件:(我们可以看到是汇编语言)
    在这里插入图片描述

    3、汇编

    将汇编指令转换为机器代码的过程,生成二进制文件,具体操作如下:
    首先输入dos指令:g++ -c helloWorld.s -o helloWorld.o ,这意味着在输入命令后会生成 .s 文件,如下:
    在这里插入图片描述
    以及生成的.o 文件:(这个文件不用去管,只是生成的二进制文件)
    在这里插入图片描述

    4、链接

    这是编译的最后一步,见名知意,就是链接的过程,链接的是库文件,将生成的目标文件转化为可执行文件(.exe),具体操作如下:
    首先输入dos指令:*g++ helloWorld.o -o helloWorld.exe * ,这意味着在输入命令后会生成 .exe 文件,如下:
    在这里插入图片描述
    以及生成的.exe文件:
    在这里插入图片描述

    5、运行可执行文件

    生成的.exe文件可以直接运行,双击图标或者输入命令:helloWorld .exe ,运行结果如下:
    在这里插入图片描述
    至此,整个编译过程结束,还想要说明的一点就是在一些IDE(继承开发环境)中,比如Visual Stdio ,我们可能不会亲自输入命令分别进行上述步骤,但是当我们点击编译的时候,IDE是默认的顺序执行上述的几个步骤(不包括执行)。

    参考链接:https://blog.csdn.net/ytx2014214081/article/details/78262196

    展开全文
  • 编译

    2020-06-21 00:51:47
    问题解答 (1): cpu 可以解析运行的程序形式称为什么代码? 1.... C语言中,通过编译.c文件,得到.obj目标文件.目标文件的内容就是本地代码 (4): 把多个文件收录在一起的文件称为什么? 1. 库文件 2.
  • 什么是编译

    千次阅读 2020-08-13 22:32:38
    什么是编译 编译就是将高级语言翻译成汇编语言或者机器语言的过程 为什么要编译 高级语言不能直接在机器上执行,因为机器只能理解0和1,但是我们又不太可能直接去编写汇编语言和机器语言,所以搭建一个桥梁来连接起...
  • 编译和链接的过程

    万次阅读 多人点赞 2018-07-22 23:08:24
    程序要运行起来,必须要经过四个步骤:预处理、编译、汇编和链接。接下来通过几个简单的例子来详细讲解一下这些过程。 对于上边用到的几个选项需要说明一下。 使用 gcc 命令不跟任何的选项的话,会默认执行...
  • 编译原理学习(一)--编译以及编译过程

    万次阅读 多人点赞 2018-05-22 21:01:00
    【龙书】编译原理(第二版)学习与理解:1.也许我们这辈子都不会去实现一个编译器,但是我们至少要知道编译器是什么?为什么会需要编译器? ①编译器首先也是一种电脑程序。它会将用某种编程语言写成的源代码(原始...
  • 编译原理

    万次阅读 多人点赞 2017-12-04 21:42:58
    一、 编译程序 1、 编译器是一种翻译程序,它用于将源语言(即用某种程序设计语言写成的)程序翻译为目标语言(即用二进制数表示的伪机器代码写成的)程序。后者在windows操作系统平台下,其文件的扩展名通常为....
  • 编译原理编译原理简单介绍

    万次阅读 多人点赞 2017-05-07 13:27:20
    编译原理简单介绍编译原理简单介绍 什么叫编译程序 翻译程序 编译程序 翻译和编译的区别 编译的过程 词法分析 语法分析 语义分析和中间代码的产生 优化 目标代码生成 编译程序的结构 编译程序总框 表格与表格的管理 ...
  • 编译原理第三版课后习题

    万次阅读 多人点赞 2018-12-22 11:12:47
    编译原理课后习题 都是编译原理老师上课布置的课后习题的整理 第二章 1.P34-4 证明G(E)是二义的。 E-&gt;EOE|(E)|v|d O-&gt;+|* 2.P34-8 上下文无关文法G[S] :S-&gt;SS*|SS+|a 答:(1)S=&gt;SS*=...
  • 编译原理书籍推荐

    千次阅读 2018-09-28 13:44:20
    大学课程为什么要开设编译原理呢?这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及...
  • Android Apk 编译原理解析

    千次阅读 2017-07-14 09:44:16
    本文基于AOSP-7.1.1-R9源码分析,源码可以参见build/+/android-...对于刚开始接触系统开发的开发者来说,经常会使用如下命令编译apk或者系统固件。source build/envsetup.sh; lunch make -j8 or mmm packages/app/Se
  • 编译原理 总结

    万次阅读 多人点赞 2015-03-26 17:36:07
    一、 编译程序1、 编译器是一种翻译程序,它用于将源语言(即用某种程序设计语言写成的)程序翻译为目标语言(即用二进制数表示的伪机器代码写成的)程序。后者在windows操作系统平台下,其文件的扩展名通常为.obj。...
  • 编译原理中:短语,直接短语,句柄

    万次阅读 多人点赞 2016-12-13 19:37:55
    这几天邻近期末,感觉上了快一学期的编译原理的许多方面还是难以理解,今天早上就突然遇到了一道题,求短语,直接短语和句柄的题,突然才发现自己连这些词的定义都不清楚,于是仔细查了以下,下面分享出来:短语书上...
  • 编译原理之代码优化

    万次阅读 多人点赞 2017-12-18 10:43:49
    编译原理出于代码编译的模块化组装考虑,一般会在语义分析的阶段生成平台无关的中间代码,经过中间代码级的代码优化,而后作为输入进入代码生成阶段,产生最终运行机器平台上的目标代码,再经过一次目标代码级别的...
  • 本文主要从教材的选择,实践项目的设置以及实践课程占总评成绩的比例等方面分析和比较了国内外多所高校编译原理课程实践教学的基本情况和特点。根据我院编译原理课程开设的实际情况,提出相应的对策,实现对我院...
  • 为什么要学编译原理

    千次阅读 2018-06-23 11:03:11
    这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题...
  • c语言实现编译原理词法分析器

    万次阅读 2017-04-18 20:00:59
    词法分析器 :#include&lt;stdio.h&gt; #include&lt;conio.h&gt; #include&lt;math.h&gt; #include&lt;string.h&gt; #include&lt;stdlib.h&...char m
  • Linux C编译原理

    千次阅读 2018-09-28 22:11:17
    1.编译程序:把一种语言(源语言---高级语言)转换成另一种语言(目标语言---低级语言--&gt; 汇编或机器语言)。 2.词法分析:对输入的字符串进行扫描和分解,识别出一个个字符及其数据类型; 3.语法分析:对...
  • 编译原理之代码生成

    千次阅读 2017-12-18 16:13:41
    前面提到了经过了词法分析->语法分析->语义分析->中间代码优化,最后的阶段...之所以将编译原理分成这种多阶段多模块的组织形式,本质的考虑其实只有两个方面: 一、代码复用:尽可能在不增加程序员工作量的前提下,增
  • 编译原理入门笔记

    千次阅读 2018-05-24 20:24:37
    什么是编译原理? 编译原理这门课程本来是很多大学必修的一门课程,但是我的本科课程里面并没有安排这门课程,由于研究生需要研究这方面的基础,于是开始自学。相信很多人都知道这门课程是计算机基础课程中比较难...
  • 编译原理及交叉编译

    千次阅读 2016-05-18 16:01:23
    编译原理及交叉编译 编译原理 gcc/g++在执行编译的时候,只要分四个阶段 : 1、预处理阶段,完成宏定义和include文件展开等工作;不生成文件 [预处理器cpp] 2、根据编译参数进行不同程度的优化,...
  • 编译原理》学习体会

    千次阅读 2014-04-02 23:15:01
    编译原理一般认为是较难的一门课.从网上的评论来看,有人说学了一年半软件理论,就一门编译看不懂;有人甚至说它是大本软件课程里最难的一门;有人抱怨国内的编译教材没有一本容易懂的.从笔者学习实践来看,第一次学了一...
  • 深入浅出说编译原理(一)

    千次阅读 多人点赞 2012-05-09 07:46:53
    个人认为编译原理对于一个程序员来说很重要,可能你认为编程的时候用的都是C++、C#、Java等高级语言,至于编译原理懂与不懂并无大碍。其实不然,所谓万变不离其宗,所有高级语言的诞生都是基于最根本的编译原理的。...
  • JavaScript预编译原理分析

    万次阅读 多人点赞 2016-10-27 23:06:34
    今天用了大量时间复习了作用域,预编译等等知识 看了很多博文,翻开了以前看过的书 发现当初觉得自己学的很明白,其实还是存在一些思维误区 今晚就整理了一下凌乱的思路 先整理一下预编译的知识吧,日后有时间再...
  • 【C语言】浅析编译原理

    千次阅读 2018-05-13 22:40:51
    提到“编译原理”,大部分人的首要反应就是苦恼。确实,编译原理这一部分的内容在计算机学习中是比较难以理解的一部分。首次接触编译原理,我也感觉很复杂,难以理解。但是当看过几次之后,对于一些简单知识点的理解...
  • 编译原理实验报告

    千次阅读 2018-06-27 15:29:51
    1.2实验要求在掌握编译原理的基础上,对编译程序实例进行分析,通过编译程序的运行,检验编译程序输出结果的正确性。理论联系实际,将所学知识用到实处,进而学会怎么写编译程序。1.3实验内容...
  • 学习编译原理的意义

    千次阅读 多人点赞 2017-02-10 13:49:22
     在国内,只有一本学校会教编译原理和计算理论的课程。我们这边的招聘经验也表明,好学校学过编译原理的学生的代码能力还是非常不错的。视野也宽阔的多。我认为,学习的语言少了,只有一两门,就会容易鄙视其他的...
  • 编译过程概述: 编译程序完成从源程序到目标程序的翻译工作,是一个复杂的整体的过程。从概念上来讲,一个编译程序的整个工作过程是划分成阶段进行的,每个阶段将源程序的一种表示形式转换成另一种表示形式,各个...

空空如也

1 2 3 4 5 ... 20
收藏数 3,353,485
精华内容 1,341,394
关键字:

编译