MSDN:"尽管实际上对 C 或 C++ 中的每种指针类型构造,C# 都设置了与之对应的引用类型,但仍然会有一些场合需要访问指针类型。例如,当需要与基础操作系统进行交互、访问内存映射设备,或实现一些以时间为关键的算法时,若没有访问指针的手段,就不可能或者至少很难完成。为了满足这样的需求,C# 提供了编写不安全代码的能力。

在不安全代码中,可以声明和操作指针,可以在指针和整型之间执行转换,还可以获取变量的地址,等等。在某种意义上,编写不安全代码很像在 C# 程序中编写 C 代码。"

不安全代码必须用修饰符 unsafe 明确地标记。

这里所谓的编写"不安全代码",就是要跳出.net CLR的限制,自己进行地址分配和垃圾回收.在C++里面,我们定义一个简单的指针,至少要做三件事,1.给他分配内存,2.保证类型转换正确3将内存空间释放,而在.net环境里,CLR将程序员从这些事情里解放出来,用户不再需要直接手工地进行内存操作。但是有时候,比如调用windows底层函数,或是效率上的原因使我们需要自己去操作地址空间,本文主要是说明在c#里面指针的用法.

指针的使用、操作内存
1.& 和 *
    c++里面,我们很熟悉这两个东西.在c#里面他们也一样可以用,只不过含有他们的代码如果不在unsafe 标记下,编译器将会将它拒之门外.当然如果条件编译参数没有/unsafe 也是无法编译通过的(如果用vs.net集成编译环境,则在项目属性页-代码生成节将"允许不安全代码块"设置成true).

&可以取得变量的地址,但是并不是所有的变量,托管类型,将无法取得其地址.c#里面,普通的值类型都是可以取得地址的,比如struct,int,long等,而class是无法取得其地址的,另外string是比较特殊的类型,虽然是值类型,但它也是受管的.这里插一下另一个运算符,sizeof,它也是仅可用于unsafe模式下.

看下面这段代码,里面简单的用到了*,&,sizeof,还有c#里很少见但c++里大家很熟的->:

//代码 

    class Class1
{
struct Point
{
public int x;
public int y;
}
public static unsafe void Main()
{
Point pt = new Point();
Point* pt1 = &pt;
int* px = &(pt1->x);
int* py = &(pt1->y);
Console.WriteLine("Address of pt is :0x{0:X} ",(uint)&pt);
Console.WriteLine("size of the struct :{0} ",sizeof(Point));
Console.WriteLine("Address of pt.x is :0x{0:X} ",(uint)&(pt.x));
Console.WriteLine("Address of pt.y is :0x{0:X} ",(uint)&(pt.y));
Console.WriteLine("Address of px is :0x{0:X} ",(uint)&(*px));
Console.WriteLine("Address of py is :0x{0:X} ",(uint)&(*py));
Console.ReadLine();
}
}

我这里运行的输出结果是:

Address of pt is :0x12F698
size of the struct :8
Address of pt.x is :0x12F698
Address of pt.y is :0x12F69C
Address of px is :0x12F698
Address of py is :0x12F69C

可以看出struct的首地址与第一个成员变量的地址相同,而这个struct的长度是8个字节(=4+4).

2.fixed

虽然在unsafe模式下可以使用指针,但是unsafe的代码仍然是受管代码.CLR会对它的对象进行管理以及垃圾回收,CLR在这个过程中就会对内存进行重定位,可能过一段时间后,根据指针指向的地址就找不到原来的对象了,岂不是说指针在c#里没有什么实际的作用?别急,还有fixed.

fixed 语句设置指向托管变量的指针并在fixed里的语句块执行期间“锁定”该变量(或者是几个变量)。如果没有 fixed 语句,则指向托管变量的指针将作用很小,因为垃圾回收可能不可预知地重定位变量。(实际上,除非在 fixed 语句中,否则 C# 不允许设置指向托管变量的指针。)

看一段与刚才类似的代码,不同的地方是这里的输出地址的语句都在fixed块里.为什么不直接像第一个例子那样直接输出呢?这是因为我们对Point进行了一个小小的改动,它不再是struct了,它现在是class!它是托管类型了,它的成员都没有固定的地址.但是在fixed块里,它的地址是固定的.

//代码 

        class Point
{
public static int x;
public int y;
}
public static unsafe void Main()
{
Point pt = new Point();
int[] arr = new int[];
//如果不用fixed语句,无论是静态成员还是实例成员,都将无法取得其地址。
//int* ps = &CPoint.StaticField;
//PrintAddress(ps);
fixed (int* p = &Point.x)
Console.WriteLine("Address is 0x{0:X}",(int)p);
fixed (int* p = &pt.y)
Console.WriteLine("Address is 0x{0:X}",(int)p);
fixed (int* p1 = &arr[],p2 = arr)
{
Console.WriteLine("Address is 0x{0:X}",(int)p1);
Console.WriteLine("Address is 0x{0:X}",(int)p2);
}
Console.ReadLine();
}

3.分配内存
在堆栈上分配内存
c#提供stackalloc ,在堆栈上而不是在堆上分配一个内存块,语句为 type * ptr = stackalloc type [ expr ];它的大小足以包含 type 类型的 expr 元素;该块的地址存储在 ptr 指针中。此内存不受垃圾回收的制约,因此不必使用fixed将其固定。此内存块的生存期仅限于定义该内存块的方法的生存期。如果内存空间不足,将会抛出System.StackOverflowException异常.

以下是一段示例程序(form msdn):

//代码 

public static unsafe void Main()
{
int* fib = stackalloc int[];
int* p = fib;
*p++ = *p++ = ; //fib[0]=fib[1]=1
for (int i=; i<; ++i, ++p)
*p = p[-] + p[-];//fib[i]=fib[i-1]+fib[i-2];
for (int i=; i<; ++i)
Console.WriteLine (fib[i]);
Console.ReadLine();
}

在堆上分配内存
既然有stackalloc,有没有heapalloc呢?答案是没有,c#没有提供这样的语法.想在堆上动态分配内存,只能靠自己想办法了.

通过Kernel32.dll里的HeapAlloc()和HeapFree()可以达到这个目的.看下面的代码:

//代码 

using System;
using System.Runtime.InteropServices;
public unsafe class Memory
{
const int HEAP_ZERO_MEMORY = 0x00000008;//内存起始地址
//获得进程堆的句柄
[DllImport("kernel32")]
static extern int GetProcessHeap();
//内存分配
[DllImport("kernel32")]
static extern void* HeapAlloc(int hHeap, int flags, int size);
//内存释放
[DllImport("kernel32")]
static extern bool HeapFree(int hHeap, int flags, void* block);
static int ph = GetProcessHeap();//获得进程堆的句柄
private Memory() {}
public static void* Alloc(int size) //内存分配
{
void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
if (result == null) throw new OutOfMemoryException();
return result;
}
public static void Free(void* block) //内存释放
{
if (!HeapFree(ph, , block)) throw new InvalidOperationException();
}
}
class Test
{
unsafe static void Main()
{
byte* buffer = (byte*)Memory.Alloc();
for (int i = ; i < ; i++)
buffer[i] = (byte)i;
for (int i = ; i < ; i++)
Console.WriteLine(buffer[i]);
Memory.Free(buffer);
Console.ReadLine();
}
}

C#之不安全代码的更多相关文章

  1. 日期格式代码出现两次的错误 ORA-01810

    错误的原因是使用了两次MM . 一.Oracle中使用to_date()时格式化日期需要注意格式码 如:select to_date('2005-01-01 11:11:21','yyyy-MM-dd ...

  2. 可爱的豆子——使用Beans思想让Python代码更易维护

    title: 可爱的豆子--使用Beans思想让Python代码更易维护 toc: false comments: true date: 2016-06-19 21:43:33 tags: [Pyth ...

  3. iOS代码规范(OC和Swift)

    下面说下iOS的代码规范问题,如果大家觉得还不错,可以直接用到项目中,有不同意见 可以在下面讨论下. 相信很多人工作中最烦的就是代码不规范,命名不规范,曾经见过一个VC里有3个按钮被命名为button ...

  4. Jquery的点击事件,三句代码完成全选事件

    先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...

  5. redux-amrc:用更少的代码发起异步 action

    很多人说 Redux 代码多,开发效率低.其实 Redux 是可以灵活使用以及拓展的,经过充分定制的 Redux 其实写不了几行代码.今天先介绍一个很好用的 Redux 拓展-- redux-amrc ...

  6. 编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议75~78)

    建议75:集合中的元素必须做到compareTo和equals同步 实现了Comparable接口的元素就可以排序,compareTo方法是Comparable接口要求必须实现的,它与equals方法 ...

  7. 使用 .NET WinForm 开发所见即所得的 IDE 开发环境,实现不写代码直接生成应用程序

    直接切入正题,这是我09年到11年左右业余时间编写的项目,最初的想法很简单,做一个能拖拖拽拽就直接生成应用程序的工具,不用写代码,把能想到的业务操作全部封装起来,通过配置的方式把这些业务操作组织起来运 ...

  8. jsp前端实现分页代码

    前端需要订一page类包装,其参数为 private Integer pageSize=10; //每页记录条数=10 private Integer totalCount; //总记录条数 priv ...

  9. 【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】

    说17号发超简单的教程就17号,qq核审通过后就封装了这个,现在放出来~~ 这个是我封装的一个开源项目:https://github.com/dunitian/LoTQQLogin ————————— ...

  10. 【.net 深呼吸】限制执行代码的权限

    前面好几篇文章,老周都跟大伙伴们聊了跟应用程序域有关的话题,干脆咱们一聊到底吧,做学问就应该这样,有恒心. App Domain的创建新应用程序域的方法中,有一个特殊的重载: public stati ...

随机推荐

  1. javaAPI中的常用 类 以及接口

    java.lang包中的常用类以及接口 类 1. Integer :Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的字段. 2. Math ...

  2. JavaScript(第二十三天)【事件入门】

    JavaScript事件是由访问Web页面的用户引起的一系列操作,例如:用户点击.当用户执行某些操作的时候,再去执行一系列代码.   一.事件介绍 事件一般是用于浏览器和用户操作进行交互.最早是IE和 ...

  3. 利用jmeter进行数据库测试

    1.首先,用jmeter进行数据库测试之前,要把oracle和mysql的JDBC驱动jar包放到jmeter安装路径的lib目录下,否则会提示错误 2.添加一个线程组,如下图 3.接下来添加一个JD ...

  4. C语言——第十四、十五周作业

    题目 题目一:交换最小值和最大值 1.实验代码 #include<stdio.h> int main() { ; int i , n; int a[N]; int x , y; scanf ...

  5. SQLite 带你入门

    SQLite数据库相较于我们常用的Mysql,Oracle而言,实在是轻量得不行(最低只占几百K的内存).平时开发或生产环境中使用各种类型的数据库,可能都需要先安装数据库服务(server),然后才能 ...

  6. 基于ssm的poi反射bean实例

    一:该例子是笔者在实际项目应用过程中,针对项目完成的一套基于poi的导入导出例子,其中一些与项目有关的代码大家直接替换成自己的需求即可. 二:笔者在项目中使用的是poi的XSSF,对应maven的po ...

  7. c 语言常量

    1,整数常量 整数常量可以是十进制.八进制或十六进制的常量.前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制. 整数常量也可以带一个后缀,后缀是 U 和 L 的组合 ...

  8. 3-51单片机WIFI学习(开发板8266底层源码介绍)

    上一篇链接  http://www.cnblogs.com/yangfengwu/p/8743502.html 直接上源码:注意源码有两部分,第一部分是一开始的时候写在模块内部的,另一部分是存在手机内 ...

  9. ORA-12514:TNS:lisntener does not currently know of service requested in connect descriptor

    在使用工具连接oracle库的时候出现了异常 根据理解初步估计是服务或者监听器没有启动 于是链接到数据库服务器进行查看  服务都已经开启,重启后链接依旧出现上述问题 使用lsnrctl status  ...

  10. confluence搭建详情

    Confluence安装&破解&汉化 编辑时间: 2017年7月7日18:01:13 1.介绍 Atlassian Confluence(简称Confluence)是一个专业的wiki ...