CIL - 变量类型

2018/06/29

在正式接触 CIL 之前,我们需要先了解一下 CIL 的运行模型。CIL 是一种面向栈设计的语言,我们可以通过 CIL 提供的很多个操作符来对一个栈进行操作,例如,将一个值推入栈的末尾,或者,把栈末尾的值弹出。他的规则设计的如此简单,以至于让第一次见到它的人感到无所适从。不过这并不重要,我们可以先从 CIL 的变量类型开始上手。

在 C# 中,我们可以使用大量的基础预置类型,比如,intlongshortboolstring 等。但在 CIL 中,基础类型就没有这么丰富了。考虑到不仅要在 CIL 之上运行 C#、F#、VB、Python、C++ 等多种语言编写的程序,而且 CIL 程序集还需要跨平台执行,CIL 就只预置提供了非常基础的几种类型:

  1. 数值类型:int32, int64, native int, F
  2. 对象引用类型:O
  3. 指针类型:native unsigned int, &

CIL 预设的这几种类型不仅满足 CTS(Common Type System,通用类型系统) 的要求,而且同时符合 CLS(Common Language Specification,公共语言规范) 对语言互操作性的规范,所以上面的这几种类型也被称为“基本 CIL 类型”。

数值类型

虽然在 C# 中我们可以使用各种各样的数值类型,但在 CIL 中,只能对 int32int64 进行操作,这是因为 CIL 的求值栈中只能存放 4 字节或者 8 字节的值。然而,CIL 中其他的地方,比如函数参数、本地变量、静态字段、数组元素等位置中是可以存储单字节或者双字节的值的。所以,当 CIL 要使用到这些短于 4 字节的值的时候,会自动的把他们转换成 4 字节来进行操作,而需要存储短字节的值的时候,会把求值栈上的值自动截断。

P.S. CIL 会把 boolchar 类型分别作为单字节值与双字节值处理

这就解释了为什么默认情况下 C# 中的 short 类型不会产生溢出错误,同时 CIL 也提供了检查一个 short 类型的值是否因为自动截断而产生溢出的问题的操作符,对应到 C# 里面就是 checked 关键字。

native int 的大小取决于计算机架构,所以能与 native int 直接运算的只有 int32,如果要跟 int64 的值进行算术运算,需要先把 native int 的转换为 int64

int32int64 的大小跟计算机架构无关,int32 类型与 int64 类型进行算术运算前也需要把 int32 转换成 int64。需要注意的是,int64 在 32 位的硬件上使用时,开销会比较大,而 int32 在任何硬件上都有不错的性能保证。

上面提到了,CIL 中的基本类型是符合 CLS 规范的,但无符号数值类型并没有被包含在 CLS 中,所以,CIL 也没有提供无符号类型,它只是提供了特殊的操作符来把求值栈上面的值当作无符号数值来处理。

浮点数是最麻烦的数据类型了,不管是在“计算机组成原理课”中还是在 CIL 中。通常,我们只需要知道在求值栈上会出现的浮点数类型有:float32float64,而在其他地方浮点数会使用平台的具体实现来存储,其中的转换问题并不需要我们考虑。在 CIL 中,float32float64 都被称为 F

对象引用类型

对象引用类型看起来就比上面提到的数值类型要简单多了,除了相等性比较之外,没有其他的操作符能对两个对象引用进行操作,而且对象引用之间也不能相互转换。

指针类型

在 CIL 中主要有两种指针类型:托管指针跟非托管指针。非托管指针类似于 C++ 中的指针一样,我们可以把在 C++ 中对指针进行的操作也用在 CIL 中的非托管指针上,比如 p++。托管指针以及其指向的内存是收 GC 控制的,如果我们对托管指针进行算术运算,这可能会引起 GC 的故障。

托管指针只能指向变量、函数参数、对象或结构体的字段等位置,并且,托管指针本身不能为 null ,就算托管指针不指向任何内存。