(一)后台内存管理

1、值数据类型

Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64位更多,这个内存包括可执行代码和加载的DLL,以及程序运行时使用的变量内容)。

在处理器的虚拟内存中,有一个区域称为栈。栈存储不是对象成员的值数据类型。

释放变量时,其顺序总是与它们分配内存的顺序相反,这就是栈的工作方式。

程序第一次运行时,栈指针指向为栈保留的内存块末尾。栈实际上是向下填充的,即从高内存地址向低内存地址填充。当数据入栈后,栈指针就会随之调整,以始终指向下一个空闲单元。

2、引用数据类型

托管堆使用一个方法(new运算符)来分配内存,再方法退出后很长一段时间存储其中的数据仍可用。与栈不同,堆上的内存是向上分配的。

建立引用变量的过程要比建立值类型的过程更复杂,其不能避免性能的系统开销。当一个引用变量超出作用域时,它会从栈中删除,但引用的数据仍保留在堆中,一直到程序终止,会垃圾回收器删除它为止,而只有在改数据不再被任何变量引用时,它才会被删除。

3、垃圾回收器

垃圾回收器释放了能释放的所有对象,就会把其他对象移动回堆的端部,再次形成一个连续的块。

堆的第一部分称为第0代,创建的新对象会移动到这个部分。垃圾回收器每运行一次后保留的对象被压缩后移动到下一代存放部分。

在.NET下,较大对象有自己的堆,称为大象堆。使用大于85000个字节的对象时,它们就会放到这个特殊的堆上。

第二代和大象堆上的回收现在放在后台线程上进行。

GCSettings.LatencyMode属性可以控制垃圾回收器进行垃圾回收的方式。

(二)释放非托管资源

垃圾回收器不知道如何释放非托管资源(文件句柄、网络连接、数据库连接),需要制定专门的规则,确保非托管资源在回收类的一个实例时释放。

  • 声明一个析构函数(或终结器),作为类的成员
  • 实现IDisposable接口

1、析构函数

析构函数的语法,没有返回类型,不带参数,没有访问修饰符,与类同名前面有一个波形符(~)。

class MyClass
{
~MyClass(){
//析构函数
}
}

C#析构函数无法确定何时执行。C#析构函数的实现会延迟对象最终从内存中删除的时间。

2、IDisposable接口

C#中,推荐使用System.IDisposable接口替代析构函数。IDisposable接口声明了一个Disposable()方法,它不带参数,返回void。

class MyClass : IDisposable
{
public void Dispose()
{
//释放
}
}

调用Dispose()方法:

MyClass my = new MyClass();
//代码
my.Dispose();

这种释放方式,如果在过程代码中抛出异常,Dispose()方法就没有被调用,导致内存没有被释放掉。

MyClass my = new MyClass();
try
{
//代码
}
finally
{
my.Dispose();
}

通过以上调用方式,可以避免过程代码抛出异常,导致内存没被释放掉。还可以使用using关键字来简化调用,效果同上面一样。

using (MyClass my = new MyClass())
{
//代码
}

(三)不安全代码

1、用指针直接访问内存

指针只是一个以与引用相同的方式存储地址的变量。

(1)用unsafe关键字编写不安全的代码

不安全代码所使用的关键字是unsafe。

unsafe class MyClass //不安全类
{
unsafe public string Name { get; set; }//不安全属性
unsafe void SayHi()//不安全方法
{
Console.WriteLine("Hi!"+ Name);
}
void SayBay()
{
unsafe int* pAge;//不安全局部变量需要在不安全方法里,这里会报错
Console.WriteLine("Bye!" + pAge);
}
}

(2)指针的语法

把代码块标记为unsafe后,使用以下语法声明指针:

int* age;

声明指针类型的变量后,就可以用与一般变量相同的方式使用它们,但首先需要学习另外两个运算符:&表示“取地址”,*表示“获取地址的内容”。

int x = 10;
int* pX = &x;
int y = *pX;

可以把指针声明为任意一种值类型。

(3)将指针强制转换为整数类型

由于指针实际上存储了一个表示地址的整数,因此任何指针中的地址都可以和任何整数类型之间相互转换。

int x = 100;
ulong* pY = (ulong*)x;

需要注意的是,在32位系统上,一个地址占4个字节,把指针转换为非uint、long或ulong时可能会导致溢出错误,64位系统一个地址占8个字节,把指针转换为非ulong时会导致溢出错误。还要注意,指针的溢出无法通过checked关键字来检查。因为.NET运行库假定,如果使用指针就知道自己在做什么,不必担心溢出。

(4)指针类型之间的强制转换

byte b = 10;
byte* pB = &b;
double* pD = (double*)pB;

(5)void指针

byte b = 10;
byte* pB = &b;
void* pV = (void*)pB;

void指针的主要作用是调用需要void*参数的API函数。

(6)指针算术的运算

可以给指针加减整数。给类型为T的指针加上数值X,其中指针的值为P,则得到的结果时P+X*(sizeof(T))。

byte b = 10;
byte* pB = &b;
pB--;

如果两个指针类型相同,则可以把一个指针减去另一个指针,结果时一个long类型值为两差值除类型所占字节数整除的结果

byte b1 = 10;
byte* pB1 = &b1;
byte b2= 11;
byte* pB2 = &b2;
long l = pB1 - pB2;

(7)sizeof运算符

使用sizeof运算符,它的参数是数据类型的名称,返回该类型所占字符数。

int x = sizeof(int);//4

(8)结构指针:指针成员访问运算符

结构指针的工作方式与预定义值类型的指针的工作方式完全相同。但是这有一个条件:结构不能包含任何引用类型,因为指针不能指向任何引用类型。

MyStruct* pStruct;
MyStruct myStruct=new MyStruct();
pStruct= &myStruct;
//通过指针访问结构成员值
(*pStruct).X = 4;
//另一种语法
*pStruct->X = 4;

(9)类成员指针

不能创建指向类的指针,这是因为垃圾回收期不维护关于指针的任何信息,只维护关于引用的信息,而在垃圾回收的过程中堆会被移动,这样就会导致指针指向错误,为了解决这个问题需要使用fixed关键字,这样告诉垃圾回收器,不移动这些对象。

MyClass myClass = new MyClass();
fixed (double* pX = &(myClass.X))//多个这样的指针可以在代码块之前放置多条
fixed (long* pX = &(myClass.Y), pZ = &(myClass.Z))//指针类型相同时可以在一个括号内声明
{
fixed (long* pW&(myClass.W))//嵌套声明
{ }
}

2、使用指针优化性能

1、创建基于栈的数组

指针的一个主要应用领域:在栈中创建高性能、低系统开销的数组。为了创建一个高性能数组,需要使用另一个关键字:stackalloc。stackalloc命令提示.NET运行库在栈上分配一定量的内存(数据类型所占字节数乘以项数)。在调用stackalloc命令时,需要提供要存储的数据类型(必须是值类型)、需要存储的数据项数。

decimal* pDecimal = stackalloc decimal[10];

项数还可以是一个变量:

int size = 5;
decimal* pDecimal = stackalloc decimal[size];

stackalloc总是返回分配数据类型的指针,它指向新分配的内存块的顶部。

要访问数组的下一个元素,可以使用指针算法。用表达式*(pDecimal+X)访问数组中下标为X的元素。

*pDecimal = 1;//数组第1项
*(pDecimal + 1) = 2;//数组第2项
C#还定义了另一种方法来访问数组,与正常的数组访问方式相同。
pDecimal[0] = 1;//等同与*pDecimal = 1;
pDecimal[1] = 2; //等同与*(pDecimal + 1) = 2;

需要注意的是,当使用指针时编译器无法检查变量,这个时候当访问项数超出分配的项数时会在运行时抛出异常。

pDecimal[20] = 21;

使用指针在获得高性能的同时,也会付出一些代价:需要确保自己知道在做什么,否则就会抛出非常古怪的运行错误。

【读书笔记】C#高级编程 第十四章 内存管理和指针的更多相关文章

  1. C#高级编程9 第14章 内存管理和指针

    C#高级编程9 内存管理和指针 后台内存管理 1) 值数据类型 在处理器的虚拟内存中有一个区域,称为栈,栈存储变量的浅副本数据,通过进入变量的作用域划分区域,通过离开变量的作用域释放. 栈的指针指向栈 ...

  2. 读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图

    读书笔记 - js高级程序设计 - 第十三章 事件   canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好   有时候即使浏览器支持,操作系统如果缺缺 ...

  3. R in action读书笔记(19)第十四章 主成分和因子分析

    第十四章:主成分和因子分析 本章内容 主成分分析 探索性因子分析 其他潜变量模型 主成分分析(PCA)是一种数据降维技巧,它能将大量相关变量转化为一组很少的不相关变量,这些无关变量称为主成分.探索性因 ...

  4. 【读书笔记】C#高级编程 第二十四章 文件和注册表操作

    (一)文件和注册表 对于文件系统操作,相关的类几乎都在System.IO名称空间中,而注册表操作由System.Win32名称空间中的类来处理. (二)管理文件系统 System.MarshalByR ...

  5. 读书笔记 - js高级程序设计 - 第十二章 DOM2和DOM3

      Node类型的变化   访问元素的样式 myDiv.style.backgroundColor = "red" myDiv.style.width = "100px& ...

  6. 【读书笔记】自然语言处理综述 -- 第四章 -- N元语法

    第四章 N元语法 本章开篇的两句话很有意思,代表了当时两个学派的思想和矛盾. 一句是"有史以来最伟大的语言学家"乔姆斯基说的:"句子的概率,在任何已知的对于这个术语的解释 ...

  7. R in action读书笔记(16)第十二章 重抽样与自助法之 置换检验

    第十二章:重抽样与自助法 本章,我们将探究两种应用广泛的依据随机化思想的统计方法:置换检验和自助法 12.1 置换检验 置换检验,也称随机化检验或重随机化检验. 有两种处理条件的实验,十个受试者已经被 ...

  8. 【读书笔记】C#高级编程 第十九章 程序集

    (一)程序集的含义 程序集是.NET用于部署和配置单元的术语. .NET应用程序包含一个或多个程序集.通常扩展名是EXE或DLL的.NET可执行程序称为程序集. 程序集是自我描述的安装单元,由一个或多 ...

  9. 【读书笔记】C#高级编程 第二十二章 安全性

    (一)身份验证和授权 安全性的两个基本支柱是身份验证和授权.身份验证是标识用户的过程,授权在验证了所标识用户是否可以访问特性资源之后进行的. 1.标识和Principal 使用标识可以验证运行应用程序 ...

随机推荐

  1. C#中的 Attribute 与 Python/TypeScript 中的装饰器是同个东西吗

    前言 最近成功把「前端带师」带入C#的坑(实际是前端带师开始从cocos转unity游戏开发了) 某天,「前端带师」看到这段代码后问了个问题:[这个是装饰器]? [HttpGet] public Re ...

  2. ServletContext 对象

    概念:代表整个Web应用 可以和程序的容器通信 (服务器) 获取 通过request对象获取  request.getServletContext(); 通过HTTPServlet获取  this.g ...

  3. 工具箱之 IKVM.NET 项目新进展

    在各种群里经常讨论的一个事情是.NET 如何调用 Java 的实现,最常见的场景之一就是在加解密方面Java提供的密钥,C#无法解密, C#中byte范围是[0,255],而Java中的byte范围是 ...

  4. CF1656E Equal Tree Sums 题解

    题目链接 思路分析 自认为是一道很好的构造题,但是我并不会做. 看了题解后有一些理解,在这里再梳理一遍巧妙的思路. 我们先来看这样的一张图: 我们发现当去掉叶子节点的父亲时,剩下树的价值和等于叶子节点 ...

  5. ATM系统开发(Java版)

    ATM系统开发 技术点分析 1.面向对象编程 每个用户的账户都是一个对象,所以需要设计账户类Accent用于创建账户对象封装账户信息. 2.使用集合容器 系统需要提供一个容器用于存储这些账户对象的信息 ...

  6. 4-10 CS后台项目练习-3 || Redis

    13. 类别管理--根据id查询类别详情--持久层 13.1. 规划SQL语句 本次需要执行的SQL语句大致是: select * from pms_category where id=? 关于字段列 ...

  7. 字符输入流Reader类和FileReader和字符输入流读取字符数据

    java.io.Reader:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类 共性成员方法: int read();读取单个字符并返回 int read(char[] ...

  8. websocket心跳实现

    简介 在实际项目中可能会使用到websocket,在使用过程中可能会存在一种问题就是,当网络异常断开时.或者websocket服务波动时,websocket会断开,导致异常,正常情况下,我们会采用心跳 ...

  9. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

  10. 个人开源项目如何上传maven中央仓库

    最近在写一些开源项目,想把自己写的东西放到maven中央仓库,提供给更多的人使用.所以写这一篇文章,记录一下自研开源项目jar包上传同步maven中央仓库成功的整个过程,这其中还是有不少的坑的. 目录 ...