CLR简介(四)

内存和类型安全

GC一个不怎么明显但是影响深远的功能就是内存安全。内存安全的意思很简单:只有程序只访问其分配(且没有被释放)的内存就是内存安全的。这意味着你不会有指向任意位置(精确来说就是过早释放的内存)的“野”(悬着的)指针。内存安全当然是我们希望所有程序都有的功能。野指针一般都是bug,而且跟踪它们相当困难。

GC 必须 保障内存安全

你可以很快看到GC对于内存安全的好处,因为其移除了用户过早释放内存(也就无法访问到没有正确分配的内存)的可能性。但另一个不怎么明显的因素是,如果你需要保证内存安全(即让程序员 不可能 创建一个内存不安全的程序),在实际操作中无法避免垃圾回收器。这是因为通常程序都需要(动态)内存分配系统,而这样对象的生命周期是任意的(与栈分配或者静态分配的内存不同,它们都是高度遵守分配协议的)。在这样不受控的环境下,通过分析程序来预测某个显式释放内存语句是否正确是不可能的。实际上,唯一判断释放语句是否正确只能在运行时做。这正是GC所做的事情(通过检查内存是否仍然有效)。因此,对于任何一个需要堆分配内存的程序来说,如果要保证内存安全,那必须使用GC。

虽然GC是保障内存安全的必要手段,但还是不够。GC无法阻止程序在数组里做越界索引或者在对象的结尾之后访问字段(可以通过对象基址和偏移量计算字段的地址做到)。当然,如果我们防范了这些情形,那么我们的确使程序员无法创建内存不安全的程序。

虽然 [通用中间语言]cil-spec 提供了存取任意内存位置的指令(即违背了内存安全原则),但它也有下列内存安全的指令集,并且CLR强烈建议使用它们:

  1. 字段访问指令集(LDFLD, STFLD, LDFLDA),根据名字读写字段地址。
  2. 数组访问指令集(LDELEM, STELEM, LDELEMA),根据索引读写一个数组元素地址。所有数组都带有指示其长度的标签,它用来在每次存取时做越界检查。

通过在用户代码中使用这些指令集,而不是底层(且不安全的)内存读写 指令集,还可以规避其他不安全 [CIL][cil-spec] 的操作(如那些允许跳转到任意且可能是非法的地址),这些都是构建一个内存安全系统所必须的。但CLR不只做这个,它支持更严谨的规则:类型安全。

类型安全是指每次内存分配都跟一个类型关联。所有操作内存的指令从理念上都与类型关联。类型安全要求读写指定内存只能使用与其关联的类型有效的指令集。这不仅保障了内存安全(没有野指针),也对每个类型加了一层额外的保护。

这些类型相关的保障中有一个重要的性质就是类型的可见性要求(特别是对于字段来说)也被强制保证了。因此,如果一个字段被声明为私有(即只能被类型本身定义的函数可见),那么这个私密性要求会被所有类型安全的代码所遵守。比如说,某个类可能定义了一个名为count的字段来记录其名为table的集合里的元素个数。假设table和count字段都是私有的,而且只有更新这两个字段的代码同时更新两个,那么table集合里元素的个数和count字段的值同步这一点有了强有力的保证。无论是否了解类型安全,程序员都是使用类型安全的概念来推理程序逻辑的。CLR将类型安全从编程语言/编译器之间的简单约定,上升到可以在运行时遵守的规范了。

可验证代码 - 强制内存和类型安全

从理念上来说,为了保证类型安全,程序执行的每个指令都需要检查其是否符合内存关联的类型要求。虽然可以在运行时做这个检查,但性能会非常慢。所以CLR采用 [CIL][cil-spec] 验证的概念,即根据[CIL][cil-spec] 静态分析程序来确认大部分指令集是类型安全的。运行时只用来补充静态分析不能检查的地方。实际上,运行时的检查次数很少。它们包括下面这些指令:

  1. 将一个基类的指针强制转换为派生类型(反过来的转换可以放在静态分析里)。
  2. 数组越界检查(如同内存安全一样的道理)。
  3. 将指针数组里的元素替换成一个新(指针)值。这点是因为CLR数组的自由转换规则(在后文分析)。

这些检查对CLR提了如下这些要求:

  1. GC里所有的内存都要关联类型(这样强制转换操作才能实现)。类型信息必须对运行时可见,而且要丰富到可以判断强制转换是否有效(即运行时需要知道类型的继承层次)。实际上,每个对象在GC堆的第一个字段就指向关联类型在运行时的数据结构对象。
  2. 所有的数组都必须包含其大小(用来做越界检查)。
  3. 数组必须知道其元素的完整类型信息。

幸运的是,有些开销很大的要求(给堆上的内存打标签)也是支持垃圾回收所必要的(GC需要知道正在扫描的对象所有字段信息),因此支持类型安全的额外成本实际上不高。

因此,按照[CIL][cil-spec]验证代码加上少量的运行时检查,CLR可以保证类型安全(和内存安全)。尽管如此,在编程弹性上,额外的安全带来严格的代价。CLR有直接的内存读写指令,为了保证代码可验证性,这些指令的使用范围很有限。如所有的指针运行都会使代码无法通过验证,因此很多C和C++的典型用法都不能在要通过验证的代码里使用;你必须使用数组。虽然这样让编码有点不舒服,但也不是很差(数组也很有用),而且好处是现成的(更少的“诡异”的bug)。

CLR强烈建议使用可验证的,类型安全的代码。即使这样,有时还是要用到无法验证的代码(主要是跟非托管代码交互)。CLR运行这样,但是最佳实践是尽量限制(类型)不安全的代码的使用。一般的程序只有极少部分的不安全代码,而其它的是类型安全代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 4,967评论 1 34
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,018评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,296评论 18 399
  • drawRect方法,会在viewWillAppear方法调用之后,viewDidAppear方法调用之前 调用。...
    爱霉霉阅读 291评论 0 2
  • 行坐在同样的位置,感受你的存在。 你在的日子,我小鸟依你。 你不在的日子,我昂头阔步。 为了更好的自己,为了我们美...
    da613594797e阅读 176评论 0 0