100165345.htm

来自「C#高级编程(第三版),顶死你们。。 。up」· HTM 代码 · 共 141 行 · 第 1/3 页

HTM
141
字号
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">如果很好地理解了内存管理和</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">提供的指针功能,也就能很好地集成</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">代码和原来的代码,并能在非常注重性能的系统中高效地处理内存。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">本章的主要内容如下:</span></p>
<p class="1" style="MARGIN-LEFT: 37.55pt; FTEL: -16.1pt"><span lang="EN-US">●<span style="FONT: 7pt &quot;Times New Roman&quot;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-FAMILY: 宋体">运行库如何在堆栈和堆上分配空间</span></p>
<p class="1" style="MARGIN-LEFT: 37.55pt; FTEL: -16.1pt"><span lang="EN-US">●<span style="FONT: 7pt &quot;Times New Roman&quot;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-FAMILY: 宋体">垃圾收集的工作原理</span></p>
<p class="1" style="MARGIN-LEFT: 37.55pt; FTEL: -16.1pt"><span lang="EN-US">●<span style="FONT: 7pt &quot;Times New Roman&quot;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-FAMILY: 宋体">如何使用析构函数和</span><span lang="EN-US">System.IDisposable</span><span style="FONT-FAMILY: 宋体">接口来确保未托管的资源的正确释放</span></p>
<p class="1" style="MARGIN-LEFT: 37.55pt; FTEL: -16.1pt"><span lang="EN-US">●<span style="FONT: 7pt &quot;Times New Roman&quot;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">中使用指针的语法</span></p>
<p class="1" style="MARGIN-LEFT: 37.55pt; FTEL: -16.1pt"><span lang="EN-US">●<span style="FONT: 7pt &quot;Times New Roman&quot;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-FAMILY: 宋体">如何使用指针实现高性能且基于堆栈的数组</span></p>
<span style="FONT-FAMILY: 宋体">
<h3 style="MARGIN-TOP: 11.4pt; MARGIN-LEFT: 0cm; MARGIN-RIGHT: 0cm; FTEL: 11.4pt"><span lang="EN-US">7.1 &nbsp;</span><span style="FONT-FAMILY: 楷体_GB2312">后台内存管理</span></h3>
<p class="MsoNormal"><a ftel="memory"><span lang="EN-US">C#</span></a><span style="FONT-FAMILY: 宋体">编程的一个优点是程序员不需要担心具体的内存管理,尤其是垃圾收集器会处理所有的内存清理工作。用户可以得到像</span><span lang="EN-US">C++</span><span style="FONT-FAMILY: 宋体">语言那样的效率,而不需要考虑像在</span><span lang="EN-US">C++</span><span style="FONT-FAMILY: 宋体">中那样内存管理工作的复杂性。虽然不必手工管理内存,但如果要编写高效的代码,就仍需理解后台发生的事情。本节要介绍给变量分配内存时计算机内存中发生的情况。</span></p>
<p class="a3" style="MARGIN-TOP: 8.15pt; FTEL: 21.45pt"><span style="FONT-FAMILY: 黑体">注意:</span></p>
<p class="a1" style="FTEL: 21.45pt"><span style="FONT-FAMILY: 楷体_GB2312">本节的许多内容是没有经过事实证明的。您应把这一节看作是一般规则的简化向导,而不是实现的确切说明。</span></p>
<h3 style="MARGIN-TOP: 8.15pt; MARGIN-LEFT: 0cm; MARGIN-RIGHT: 0cm; FTEL: 8.15pt"><a ftel="_Toc507815017"><span lang="EN-US">7.1.1&nbsp; </span></a><span style="FONT-FAMILY: 黑体">值数据类型</span></h3>
<p class="MsoNormal"><a ftel="valuetypes"><span lang="EN-US">Windows</span></a><span style="FONT-FAMILY: 宋体">使用一个系统:虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址上,这些任务完全由</span><span lang="EN-US">Windows</span><span style="FONT-FAMILY: 宋体">在后台管理,其实际结果是</span><span lang="EN-US">32</span><span style="FONT-FAMILY: 宋体">位处理器上的每个进程都可以使用</span><span lang="EN-US">4GB</span><span style="FONT-FAMILY: 宋体">的内存<span style="LETTER-SPACING: -1pt">&mdash;&mdash;</span></span><span style="LETTER-SPACING: -1pt"> </span><span style="FONT-FAMILY: 宋体">无论计算机上有多少硬盘空间。</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">在</span><span lang="EN-US">64</span><span style="FONT-FAMILY: 宋体">位处理器上,这个数字会更大</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">。这个</span><span lang="EN-US">4GB</span><span style="FONT-FAMILY: 宋体">内存实际上包含了程序的任何一部分<span style="LETTER-SPACING: -1pt">&mdash;&mdash;</span></span><span style="LETTER-SPACING: -1pt"> </span><span style="FONT-FAMILY: 宋体">包括可执行代码、代码加载的所有</span><span lang="EN-US">DLL</span><span style="FONT-FAMILY: 宋体">,以及程序运行时使用的所有变量的内容。这个</span><span lang="EN-US">4GB</span><span style="FONT-FAMILY: 宋体">内存称为虚拟地址空间,或虚拟内存,为了方便起见,我们继续把它当作一般内存来使用。</span></p>
<p class="MsoNormal"><span lang="EN-US">4GB</span><span style="FONT-FAMILY: 宋体">中的每个存储单元都是从</span><span lang="EN-US">0</span><span style="FONT-FAMILY: 宋体">开始往上排序的。要把一个值存储在内存的某个空间中,就需要提供表示该存储单元的数字。在任何高级语言中,例如</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">VB</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">C++</span><span style="FONT-FAMILY: 宋体">和</span><span lang="EN-US">Java</span><span style="FONT-FAMILY: 宋体">,编译器负责把人们可以理解的名称转换为处理器可以理解的内存地址。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在进程的虚拟内存中,有一个区域称为堆栈。堆栈存储不是对象成员的值数据类型。另外,在调用一个方法时,也使用堆栈复制传递给方法的所有参数。为了理解堆栈的工作原理,需要注意在</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">中变量的作用域。如果变量</span><span lang="EN-US">a</span><span style="FONT-FAMILY: 宋体">在变量</span><span lang="EN-US">b</span><span style="FONT-FAMILY: 宋体">之前进入作用域,</span><span lang="EN-US">b</span><span style="FONT-FAMILY: 宋体">就会先出作用域。下面的代码:</span></p>
<p class="2" style="MARGIN-TOP: 8.15pt; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int a;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // do something</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int b;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // do something else</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; LINE-HEIGHT: 13pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p class="2" style="MARGIN-TOP: 0cm; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; }</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">首先声明</span><span lang="EN-US">a</span><span style="FONT-FAMILY: 宋体">。在内部的代码块中声明了</span><span lang="EN-US">b</span><span style="FONT-FAMILY: 宋体">。然后内部的代码块终止,</span><span lang="EN-US">b</span><span style="FONT-FAMILY: 宋体">就出作用域,最后</span><span lang="EN-US">a</span><span style="FONT-FAMILY: 宋体">出作用域。所以</span><span lang="EN-US">b</span><span style="FONT-FAMILY: 宋体">的生存期会完全包含在</span><span lang="EN-US">a</span><span style="FONT-FAMILY: 宋体">的生存期中。在解除变量时,其顺序总是与给它们分配内存的顺序相反,这就是堆栈的工作方式。</span></p>
<p class="MsoNormal" style="LINE-HEIGHT: 16pt"><span style="FONT-FAMILY: 宋体">我们不知道堆栈在地址空间的什么地方,这些信息在进行</span><span lang="EN-US">C#</span><span style="FONT-FAMILY: 宋体">开发是不需要知道的。堆栈指针</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">操作系统维护的一个变量</span><span lang="EN-US">) </span><span style="FONT-FAMILY: 宋体">包含堆栈中下一个自由空间的地址。程序第一次运行时,堆栈指针指向堆栈保留的内存块末尾。堆栈实际上是向下填充的,即从高内存地址向低内存地址填充。当数据入栈后,堆栈指针就会随之调整,以始终指向下一个自由空间。这种情况如图</span><span lang="EN-US">7-1</span><span style="FONT-FAMILY: 宋体">所示。在该图中,显示了堆栈指针</span><span lang="EN-US">800000(16</span><span style="FONT-FAMILY: 宋体">进制的</span><span lang="EN-US">0xC3500)</span><span style="FONT-FAMILY: 宋体">,下一个自由空间是地址</span><span lang="EN-US">79999</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p align="center"><span lang="EN-US"><img height="130" src="07/image001.gif" width="197" alt="" /></span></p>
<p style="FTEL: 8.15pt" align="center"><span style="FONT-FAMILY: 宋体">图</span><span lang="EN-US">&nbsp; 7-1</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">在下面的代码中,我们已告诉编译器需要一些存储单元以存储一个整数和一个双精度浮点数,这些存储单元会分别分配给</span><span lang="EN-US">nRacingCars</span><span style="FONT-FAMILY: 宋体">和</span><span lang="EN-US">engineSize</span><span style="FONT-FAMILY: 宋体">,声明每个变量的代码表示开始请求访问这个变量,闭合花括号表示不再请求其他变量。</span></p>
<p class="2" style="MARGIN-TOP: 8.15pt; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int nRacingCars = 10;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; double engineSize = 3000.0;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // do calculations;</span></p>
<p class="2" style="MARGIN-TOP: 0cm; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; }</span></p>
<p class="MsoNormal" style="LINE-HEIGHT: 16pt"><span style="FONT-FAMILY: 宋体">假定使用如图</span><span lang="EN-US">7-1</span><span style="FONT-FAMILY: 宋体">所示的堆栈。变量</span><span lang="EN-US">nRacingCars</span><span style="FONT-FAMILY: 宋体">放在内存中,其值是</span><span lang="EN-US">10</span><span style="FONT-FAMILY: 宋体">,这个值放在存储单元</span><span lang="EN-US">799996~799999</span><span style="FONT-FAMILY: 宋体">上,这</span><span lang="EN-US">4</span><span style="FONT-FAMILY: 宋体">个字节就在堆栈指针所指空间的下面。有</span><span lang="EN-US">4</span><span style="FONT-FAMILY: 宋体">个字节是因为存储</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">要使用</span><span lang="EN-US">4</span><span style="FONT-FAMILY: 宋体">个字节。为了容纳该</span><span lang="EN-US">int</span><span style="FONT-FAMILY: 宋体">,应从堆栈指针中减去</span><span lang="EN-US">4</span><span style="FONT-FAMILY: 宋体">,所以它现在指向位置</span><span lang="EN-US">799996</span><span style="FONT-FAMILY: 宋体">,即下一个自由空间之后</span><span lang="EN-US">(79995)</span><span style="FONT-FAMILY: 宋体">。</span></p>
<p class="MsoNormal" style="LINE-HEIGHT: 16pt"><span style="FONT-FAMILY: 宋体">当</span><span lang="EN-US">engineSize</span><span style="FONT-FAMILY: 宋体">出作用域时,计算机就知道不再需要这个变量了。因为变量的生存期总是嵌套的,可以保证,当</span><span lang="EN-US">engineSize</span><span style="FONT-FAMILY: 宋体">在作用域中时,无论发生什么情况,堆栈指针总是会指向存储</span><span lang="EN-US">engineSize</span><span style="FONT-FAMILY: 宋体">的空间。为了从内存中删除这个变量,应给堆栈指针递增</span><span lang="EN-US">8</span><span style="FONT-FAMILY: 宋体">,现在指向</span><span lang="EN-US">engineSize</span><span style="FONT-FAMILY: 宋体">使用过的空间。此处就是放置闭合花括号的地方,当</span><span lang="EN-US">nRacingCars</span><span style="FONT-FAMILY: 宋体">也出作用域时,堆栈指针就再次递增</span><span lang="EN-US">4</span><span style="FONT-FAMILY: 宋体">,此时如果内存中又放入另一个变量,从</span><span lang="EN-US">799999</span><span style="FONT-FAMILY: 宋体">开始的存储单元就会被覆盖,这些空间以前是存储</span><span lang="EN-US">nRacingCars</span><span style="FONT-FAMILY: 宋体">的。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">如果编译器遇到像</span><span lang="EN-US">int i</span><span style="FONT-FAMILY: 宋体">、</span><span lang="EN-US">j</span><span style="FONT-FAMILY: 宋体">这样的代码,则这两个变量进入作用域的顺序就是不确定的:两个变量是同时声明的,也是同时出作用域的。此时,变量以什么顺序从内存中删除就不重要了。编译器在内部会确保先放在内存中的那个变量后删除,这样就能保证该规则不会与变量的生存期冲突。</span></p>
<h3 style="MARGIN-TOP: 8.15pt; MARGIN-LEFT: 0cm; MARGIN-RIGHT: 0cm; FTEL: 8.15pt"><a ftel="_Toc507815018"><span lang="EN-US">7.1.2&nbsp; </span></a><span style="FONT-FAMILY: 黑体">引用数据类型</span></h3>
<p class="MsoNormal"><a ftel="new"></a><a ftel="referencetypes"></a><a ftel="heaps"><span style="FONT-FAMILY: 宋体">堆栈有非常高的性能,但对于所有的变量来说还是不太灵活。变量的生存期必须嵌套,在许多情况下,这种要求都过于苛刻。通常我们希望使用一个方法分配内存,来存储一些数据,并在方法退出后的很长一段时间内数据仍是可以使用的。只要是用</span><span lang="EN-US">new</span></a><span style="FONT-FAMILY: 宋体">运算符来请求存储空间,就存在这种可能性&mdash;&mdash;例如所有的引用类型。此时就要使用托管堆。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">如果以前编写过需要管理低级内存的</span><span lang="EN-US">C++</span><span style="FONT-FAMILY: 宋体">代码,就会很熟悉堆</span><span lang="EN-US">(heap)</span><span style="FONT-FAMILY: 宋体">。托管堆和</span><span lang="EN-US">C++</span><span style="FONT-FAMILY: 宋体">使用的堆不同,它在垃圾收集器的控制下工作,与传统的堆相比有很显著的性能优势。</span></p>
<p class="MsoNormal"><span style="FONT-FAMILY: 宋体">托管堆</span><span lang="EN-US">(</span><span style="FONT-FAMILY: 宋体">或简称为堆</span><span lang="EN-US">)</span><span style="FONT-FAMILY: 宋体">是进程的可用</span><span lang="EN-US">4GB</span><span style="FONT-FAMILY: 宋体">中的另一个内存区域。要了解堆的工作原理和如何为引用数据类型分配内存,看看下面的代码:</span></p>
<p class="2" style="MARGIN-TOP: 8.15pt; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; void DoWork()</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; {</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Customer arabel;</span></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><a ftel="OLE_LINK8"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; arabel = new Customer();</span></a></p>
<p class="2" style="MARGIN-LEFT: 21.45pt; FTEL: 18.45pt"><a ftel="OLE_LINK40"><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Customer mrJones = new Nevermore60Customer();</span></a></p>
<p class="2" style="MARGIN-TOP: 0cm; MARGIN-LEFT: 21.45pt; MARGIN-RIGHT: 0cm; FTEL: 18.45pt"><span lang="EN-US">&nbsp;&nbsp; }</span></p>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?