编译器优化

编译程序的组织和生成

十年热恋 提交于 2019-12-02 11:21:08
编译程序的组织和生成 编译程序的组织 根据系统资源的状况、运行目标的要求…… 等,可以将一个编译程序设计成多遍(Pass) 扫描的形式,在每一遍扫描中,完成不同的 任务。 如:首遍构造语法树,二遍处理中间表示,增加 信息等。 遍可以和阶段相对应,也可以和阶段无关 单遍代码不太有效 编译程序的设计目标 规模小、速度快、诊断能力强、可靠性高、可移植性好、可扩充性好 目标程序也要规模小、执行速度快 编译系统规模较大,因此可移植性很重要 为了提高可移植性,将编译程序划分为前端和 后端 前端 与源语言有关、与目标机无关的部分 词法分析、语法分析、语义分析与中间代码生 成、与机器无关的代码优化 后端 与目标机有关的部分 与机器有关的代码优化、目标代码生成 编译程序的生成 如何实现编译器? 直接用可运行的代码编制——太费力! 自举-使用语言提供的功能来编译该语言自 身。 “第一个编译器是怎样被编译的?” 1、T型图 编译原理 T 形图 源语言 表示语言 目标语言分别是什么? 编译器本身也是一个软件,该软件用何种语言编写的,比如是用c语言编写的,则c语言就是它的表示语言(一般说成实现语言)。编译系统的功能是翻译,它能把一种高级语言(即源语言)编写的程序等价的翻译成另一低级语言(即目标语言)的程序。 举例: 用 C语言实现一个java编译器,可以将java程序翻译成bytecode

__declspec关键字详细用法

廉价感情. 提交于 2019-12-01 21:10:39
__declspec用于指定所给定类型的实例的与 Microsoft 相关的存储方式。其它的有关存储方式的修饰符如 static 与 extern 等是 C 和 C++ 语言的 ANSI 规范,而 __declspec 是一种扩展属性的定义。扩展属性语法简化并标准化了 C 和 C++ 语言关于 Microsoft 的扩展。 用法: __declspec ( extended-decl-modifier ) extended-decl-modifier参数如下,可同时出现,中间有空格隔开 : align ( C++ ) allocate appdomain deprecated ( C++ ) dllimport dllexport jitintrinsic naked ( C++ ) noalias noinline noreturn nothrow ( C++ ) novtable process property( C++ ) restrict selectany thread uuid( C++ ) 1.__declspec关键字应该出现在简单声明的前面。对于出现在 * 或 & 后面或者变量声明中标识符的前面的 __declspec ,编译器将忽略并且不给出警告。 2.要注意区分 __declspec 是修饰类型还是修饰变量: __declspec(align(8))

Java虚拟机:源码到机器码

▼魔方 西西 提交于 2019-12-01 19:51:00
目录 前端编译器:源代码到字节码 JIT编译器:从字节码到机器码 AOT编译器:源代码到机器码 总结 转载 无论什么语言写的代码,其到最后都是通过机器码运行的,无一例外。那么对于 Java 语言来说,其从源代码到机器码,这中间到底发生了什么呢?这就是今天我们要聊的。 如下图所示,编译器可以分为:前端编译器、JIT 编译器和AOT编译器。下面我们逐个讲解。 前端编译器:源代码到字节码 对于 Java 虚拟机来说,其实际输入的是字节码文件,而不是 Java 文件。那么对于 Java 语言而言,其实怎么将 Java 代码转化成字节码文件的呢?我们知道在 JDK 的安装目录里有一个 javac 工具,就是它将 Java 代码翻译成字节码,这个工具我们叫做编译器。相对于后面要讲的其他编译器,其因为处于编译的前期,因此又被称为前端编译器。 我们运行 javac 命令的过程,其实就是 javac 编译器解析 Java 源代码,并生成字节码文件的过程。说白了,其实就是使用 javac 编译器把 Java 语言规范转化为字节码语言规范。javac 编译器的处理过程可以分为下面四个阶段: 第一个阶段:词法、语法分析。在这个阶段,JVM 会对源代码的字符进行一次扫描,最终生成一个抽象的语法树。简单地说,在这个阶段 JVM 会搞懂我们的代码到底想要干嘛。就像我们分析一个句子一样,我们会对句子划分主谓宾

C#8.0 新增功能

扶醉桌前 提交于 2019-12-01 18:53:29
01、Readonly成员   可将readonly修饰符应用于结构的任何成员,它指示该成员不会修改状态。这比将readonly修饰符应用于struct声明更精细。 public struct Point { public double X { get; set; } public double Y { get; set; } public double Distance => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X}, {Y}) is {Distance} from the origin"; } 像大多数结构一样ToString()方法不会修改状态。可以通过readonly修饰符添加到ToString()的声明来对此进行指示: public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin"; 上述更改会发生编译器警告,因为ToString访问Distance属性,该属性未标记为readonly,如下图所示: 需要创建防御性副本时,编译器会发出警告,Distance属性不会更改状态,因此可以通过将readonly修饰符添加到声明来修复此警告: public

C/C++ Volatile关键词深度剖析

别来无恙 提交于 2019-12-01 08:14:32
背景 此微博,引发了朋友们的大量讨论:赞同者有之;批评者有之;当然,更多的朋友,是希望我能更详细的解读C/C++ Volatile关键词,来佐证我的微博观点。而这,正是我写这篇博文的初衷:本文,将详细分析C/C++ Volatile关键词的功能 (有多种功能)、Volatile关键词在多线程编程中存在的问题、Volatile关键词与编译器/CPU的关系、C/C++ Volatile与Java Volatile的区别,以及Volatile关键词的起源,希望对大家更好的理解、使用C/C++ Volatile,有所帮助。 Volatile,词典上的解释为:易失的;易变的;易挥发的。那么用这个关键词修饰的C/C++变量,应该也能够体现出”易变”的特征。大部分人认识Volatile,也是从这个特征出发,而这也是本文揭秘的C/C++ Volatile的第一个特征。 Volatile:易变的 在介绍C/C++ Volatile关键词的”易变”性前,先让我们看看以下的两个代码片段,以及他们对应的汇编指令 (以下用例的汇编代码,均为VS 2008编译出来的Release版本): 测试用例一:非Volatile变量 b = a + 1;这条语句,对应的汇编指令是:lea ecx, [eax + 1]。由于变量a,在前一条语句a = fn(c)执行时,被缓存在了寄存器eax中,因此b = a + 1

jit编译

和自甴很熟 提交于 2019-12-01 07:29:42
热点代码: 虚拟机中的字节码(.class文件内容)是由解释器( Interpreter )完成编译的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为“热点代码”。 什么是jit编译: 为了提高热点代码的执行效率,在运行时,即时编译器(JIT)会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,然后保存到内存中。 在 HotSpot 虚拟机中,内置了两个 JIT,分别为 C1 编译器和 C2 编译器,这两个编译器的编译过程是不一样的。 C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI 应用对界面启动速度就有一定要求。 C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性,这两种即时编译也被称为 Client Compiler 和 Server Compiler。 如何探测代码为热点代码: 热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。 虚拟机为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter;在程序中遇到控制流向后跳转的指令称为

【Java基础】Java中的语法糖

吃可爱长大的小学妹 提交于 2019-12-01 07:01:01
目录 Java中的语法糖 switch对String和枚举类的支持 对泛型的支持 包装类型的自动装箱和拆箱 变长方法参数 枚举 内部类 条件编译 断言 数值字面量 for-each try-with-resource Lambda表达式 Lambda表达式的语法 基本的Lambda例子(实现功能接口) 使用Lambdas排序集合 使用Lambdas和Streams 字符串对+号的支持 参考 语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言本身功能来说没有什么影响,只是为了方便程序员的开发,提高开发效率。说白了,语法糖就是对现有语法的一个封装。 但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。所以真正支持语法糖的是Java编译器。 Java中的语法糖 switch支持String和枚举 泛型 自动装箱与拆箱 方法变长参数 枚举 内部类 条件编译 断言 数值字面量 for-each try-with-resource Lambda表达式 字符串+号。 switch对String和枚举类的支持 switch对枚举和String的支持原理其实差不多。switch原生支持只能支持比较整数类型。如果switch后面是String类型的话

读书笔记《深度探索c++对象模型》(6) - 执行期语意

只谈情不闲聊 提交于 2019-12-01 05:38:45
一、对象的构造与析构 因声明定义一个类对象,若需要构造或析构函数时,其构造函数和析构函数将被编译器插入到代码中的合适位置,然而因为需要对析构函数的正确且合理的位置调用,可能会出现多个可能的位置插入析构函数的调用代码,如不同处的 return , goto 语句、 {}代码段结束位置等复杂场景中,故建议定义一个类对象时最好放置在需要使用的地方且防止可能在多处插入析构函数的情况,如可用{}早点儿结束析构操作,即最好不要将类对象声明定义在最开始处。 全局对象, C++ 全局对象均放置在数据段中,且被初始化为给定的初值或默认值为 0 ;故而一般情况下全局对象在进入 main 函数前已被初始化,而 main 函数结束时把其析构释放掉;故而需要编译器提供必要的静态初始化的支持,即全局对象的静态初始化以及对应的析构释放。 全局对象应需要被静态初始化,其不能被放置在 try 区段内,对静态调用的构造函数可能会抛异常时将引发 terminate 函数调用;另外便是需要控制跨模块做静态初始化的对象的依赖顺序产生的问题和复杂度,建议不要用需要做静态初始化的全局对象。 局部静态对象,需要保证其所在函数被调用多次时,始终仅调用一次构造函数和析构函数,需要编译器来支持实现,插入必要的初始化一次和析构一次的临时保护代码,不同编译器有不同的实现方式(可能通过插入另外一个全局静态的一个对象

读书笔记《深度探索c++对象模型》(3) - 类成员变量的数据语意

半世苍凉 提交于 2019-12-01 04:28:29
一、类数据成员绑定   1.一个空类的大小不会为空,一般为一个char,即1个字节大小,其为了区分不同两个空类对象,需要一个地址来表示。   2.一个类的sizeof的大小,由多个方面影响:     1)语言本身的负担(如虚指针)     2)不同编译器对特殊情况的优化处理(如继承于一个空类的类,编译器可能做优化)     3)alignment对齐方式的限制(因为对齐的原因,成员变量的顺序有时也会对sizeof大小影响)。   3.由typedef重声明的类型在类外前面和类中均有时,则编译器可能会按照第一次被决议的那个typedef使用,因此此处存在一个缺陷,即在未发现到类中的typedef时,均使用外层的,导致类型不是预期的类中的那个类型,故而一般情况下,类中的typedef应该放在类中的起始位置处最为安全,而不是声明成员变量处;以此来解决数据成员类型绑定的问题。 二、类数据成员布局   1.非静态的数据成员在类对象中的排列顺序与其声明顺序一致,虽然中间可能会插入一些对齐需要的字节,另外一个控制访问段(public,private,protected)中的数据成员顺序一致,但不同的控制访问段的数据成员间的顺序不一定与类中声明的顺序一致。   2.类中若是有vptr,则vptr的位置可能位于类对象中的前端也可能在最后段,甚至也有可能在数据成员的中间。不过一般为最前或最后。   3

const、volatile、mutable的用法

六眼飞鱼酱① 提交于 2019-11-30 17:15:47
const 、 volatile 、 mutable 的 用法 const 修饰普通变量和指针 const 修饰变量,一般有两种写法: const TYPE value; TYPE const value; 这两种写法在本质上是一样的。它的含义是: const 修饰的类型为 TYPE 的变量 value 是不可变的。对于一个非指针的类型 TYPE ,无论怎么写,都是一个含义,即 value 值不可变。 例如: const int nValue ; //nValue 是 const int const nValue ; //nValue 是 const 但是对于指针类型的 TYPE ,不同的写法会有不同情况: l 指针本身是常量不可变 (char*) const pContent; l 指针所指向的内容是常量不可变 const (char) *pContent; (char) const *pContent; l 两者都不可变 const char* const pContent; 识别 const 到底是修饰指针还是指针所指的对象,还有一个较为简便的方法,也就是沿着 * 号划一条线: 如果 const 位于 * 的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量; 如果 const 位于 * 的右侧, const 就是修饰指针本身,即指针本身是常量。 const