Skip to content

Commit

Permalink
update 04.1-Bytecode
Browse files Browse the repository at this point in the history
  • Loading branch information
Henry-T committed Nov 26, 2014
1 parent 84ee8af commit 43911c8
Showing 1 changed file with 114 additions and 0 deletions.
114 changes: 114 additions & 0 deletions 04.1-Bytecode.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,117 @@ private:
- **流程控制.** 我们的例子中没有这部分,但如果你想要让指令有选择地执行或是循环重复执行,那你就需要流程控制。在字节码这样的底层语言中,它们及其简单——跳转。
在我们的指令循环中,我们有一个索引指向字节码堆栈的当前位置。换句话说,它是个goto。你可以用它来实现任何高级流程。
- **抽象.** 如果你的用户开始定义很多数据,最终他们会希望能重用字节码而不是反复复制粘贴。你也许会用到可调用过程。
最简情况下,过程仅仅是一个跳转。唯一的不同是虚拟机维护另一个返回堆栈。当它执行到一个“call”指令时,它将当前指令压入返回栈中然后跳转到被调用的字节码。当它遇到一个“return”时,虚拟机从返回栈中弹出索引并跳转回索引的位置。

### 值应当如何表示?

我们的实例虚拟机只助理一种值,整数。这让答案变得很简单——这个堆栈仅仅是个存放int的栈。一个功能完善的虚拟机应当支持不同的数据类型:字符串、对象、列表等等。你必须决定如何在内部存储它们。

- **单一数据类型:**
- 它很简单。你不用担心标签、转换或者类型检查。
- 你无法使用不同的数据类型。这个缺陷太明显了。将不同的类型填入到一种单一的呈现方式中——例如将数字存储成字符串——这是自找麻烦。
- **标签的一个变体:**
这是动态类型语言通用的形式。每个值都由两部分组成。第一部分是一个标签——一个枚举——用来标志所存储数据的类型。剩下的位根据这个类型来解析,例如:
```
enum ValueType
{
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING
};
struct Value
{
ValueType type;
uni on
{
int intValue;
double double Value;
char* string Value;
};
};
```
- 值知道他们的类型。这种呈现方式的好处是,能够在运行时对值的类型做检查。这对动态调用很重要并能够保证你不会把操作执行到不支持它们的类型上。
- 占用内存更多。每一个值必须携带标志它们类型的额外位。在虚拟机这样的底层中,这几个位的占用增长得很快。
- **不代标签的联合体:**
与前一种方式类似,但使用联合体,它不会在每个值上携带一个累型标签。你有一个小数据块去表示多种类型,你需要自行确保值能得到正确解析,你不需要在运行时检查类型。
> 这也是无类型语言比如汇编和Forth的储值方式。这些语言让用户自己保证解析值的方式是正确的。玻璃心伤不起!
- 紧密。没有比只存储值本身更加高效的储值方式了。
- 快速。没有类型标签意味着你也无需再运行时检查它们。这也是静态类型语言比动态类型语言快的原因。
- 不安全。当然,这是真正的代价。一段错误的字节码,让你把一个数字当做指针或者反过来,都会违反游戏的安全性而造成崩溃。
> 如果你的字节码是从静态类型语言编译而来的,你可能会因编辑器不会生成不安全字节码而认为它是安全的。这也许是正确的,但是不要忘了用户可能绕过你的编译器去手工编写一些恶意的字节码。
> 这就是Java虚拟机等要在加载程序时执行字节码检查的原因。
- **一个接口:**
确定值是一些类型中的一种的面向对象的解决方案是多态。一个接口提供进行各种类型测试和转换的虚方法,像下面这样:
```
class Value
{
public:
virtual ~Value() {}
virtual ValueType type() = 0;
virtual int asInt() {
// Can only call this on ints.
assert(false);
return 0;
}
// Other conversi on methods...
};
```
你可能像下面这样定义数据的类:
```
class IntValue : public Value
{
public:
IntValue(int value):value_(value)
{}
virtual ValueType type() { return TYPE_INT; }
virtual int asInt() { return value_; }
private:
int value_;
};
```
- 开放式。你可以再核心虚拟机之外定义任何实现基础接口的数据类型。
- 面向对象。如果你采用面向对象的准则,正确的做法是对类型采取多态性调度,而不是对类型标签是用switch。
- 累赘。你得为每一个数据类型定义一个类,并在里面填写一些重复性的内容。在前一个例子中,我们定义了所有的类型,这个例子里才只定义了一个!
- 低效。为了实现多态,你得借助于指针。这意味着像布尔和数字这种微小的值也要被封装到对象中,并在堆上面分配。每次访问一个值,你都是在做虚函数调用。
在虚拟机核心中,这样影响效率的点会不断累加。事实上正因为这些问题,导致我们避免解释器模式,唯一的区别是解释器处理的是代码而我们处理的是值。
我的建议是,如果你能坚持使用单一数据类型,那就这么做。否则,使用标签联合体。这是几乎所有编程语言的解析的方式。
### 如何生成字节码?
我把最重要的问题留到了最后。我带你消化并分析了字节码,但是轮到你做些东西来生成它们了。标准的解决方案是编写一个编译器,但这并不是唯一的途径。
- **如果你定义了一种基于文本的语言:**
- 你得定义一种语法。无论业余或专业的设计师都容易想当然得低估这件事的难度。定义一种对*分析器*友好的语法很容易,但是定义一种对*用户*友好的很难。
语法设计也是种用户界面设计,及时用户界面变成了一串字符,也容易不到哪儿去。
- 你要实现一个分析器。不管它们的名声怎么样,这部分很简单。你可以使用ANTLR或Bison这样的解析器生成器,或者——跟我一样——自己写一个好用的递归分析,这样就行了。
- 你必须处理语法错误。这是整个过程中最重要也是最难的部分。当用户出现语法或语义错误的时候——他们当然会,而且一直出错——将它们领回到正确的道路上是你的事情。当你都不知道解释器处于一个意外符号上时,提供帮助性的反馈并不容易。
- 对非技术人员没有亲和力。程序员喜欢文本文件。配合强大的命令行工具,我们将它们当做计算机里的乐高块——简单,却有无数种组合方式。
多数非程序员并不这样看待纯文本。对他们来说,文本文件如同给一个机器稽核员填写的纳税表,即使少填一个分好,它也会朝你大叫。
- **如果你定义一个图形化编辑工具:**
- 你要实现一个用户界面。按钮、点击、拖拽等诸如此类。这个方法感觉有点低三下四,但是我个人很喜欢它。如果你选择这个方向,设计好用户界面就是做好这件事情的关键——不是一件能应付了事的无聊事。
这里你做的没一点儿额外工作都会使得工具更加易用而友好,这会直接提高你游戏内容的质量。如果你回头看看很多你喜欢的游戏,你常常会发现它们的秘密是有一个又去的编辑工具。
- 不易出错。因为用户一步步交互式得构建行为,你的程序能够在发现错误时立刻引导他们改正。
使用文本语言时,工具只有在提交整个文件时才能看到用户内容。这使得避免和控制错误都变得困难。
- 可移植性差。文本编译器的一点好处是它是通用的。一个简单的编译器仅仅读取一个文件并输出另一个文件。在操作系统间移植是很容易的。
> 除了换行符和编码。
当你制作UI时,你得选择使用什么框架,很多框架都依赖于一种操作系统。也有一些跨平台的UI工具包,但是他们的代价在于亲切感——他们在所有的平台上都让人感到陌生。
## 参考
- 这个模式是四人帮[解释器模式](http://en.wikipedia.org/wiki/Interpreter_pattern)的姐妹版。它们都会给你一种用数据来组合行为的方法。
事实上,你经常会两个模式一起使用。你用来生成字节码的工具通常会有一个内部对象树来表达代码。这正是解释器模式能做的事情。
为了将它编译成字节码,你需要递归遍历整棵树,正如你在解释器模式中解析它那样。唯一的不同是你并不是直接执行一段代码而是将它们输出成字节码指令并在以后执行它们。
- [Lua](http://www.lua.org/)编程语言是游戏中广泛使用的编程语言。它内部实现了一个紧凑的基于寄存器的字节码虚拟机。
- [Kismet](http://en.wikipedia.org/wiki/UnrealEd#Kismet)是内置在UnrealEd(Unreal Engine 的编辑器)中的图形化脚本工具。
- 我自己的小脚本语言,[Wren](https://github.com/munificent/wren),是一个简单的基于堆栈的字节码解释器。
===============================
[上一节](04-Behavioral Patterns.md)
[目录](README.md#目录)
[下一节](04.2-Subclass Sandbox.md)

0 comments on commit 43911c8

Please sign in to comment.