为了保持类型安 全,默认情况下,C# 不支持指针算法。 不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。
unsafe 在C# 程 序中的使用场合:
1)实时应用,采用指针来提高性能;
2)引用非.net DLL提供的如C++编写的外部函数,需要指针来传递该函数;
3)调试,用以检测程序在运行过程中的内存使用状况。
使用unsafe 的利弊:
好处:性能和灵活性提高;可以调用其他dll的函数,提高了兼容性;可以得到内存地址;
坏处:非法修改了某些变量;内存泄漏。
unsafe 与unmanaged的区别:
managed code是在CLR监管下运行的程序。以下任务由CLR来执行:管理对象内存,类型安全检测和冗余处理。
unmanaged code也就是能由程序员直接进行内存操作的程序。
unsafe 是介于managed和unmanaged之间的桥梁,它使得managed code也能使用指针来控制和操作内存。
unsafe 的使用:
unsafe 可以用来修饰类、类的成员函数、类的全局变量,但不能用来修饰类成员函数内的局部变量。 编译带有unsafe 代码的程序也要在 “configuration properties>build” 中把允许unsafe 代码设为真。
但是在managed code中使用unsafe 时也要注意,正因为CLR可以操作内存对象,假如你写了一下代码:
public unsafe
void add(int *p)
{
*p=*p+4;
}
p的地址值可能会在运行过程中被CLR所修改,这通常可采用fixed来处理,使指针所指向的地址不能被改变。如下:
fixed(int *p=& value)
{
add(p);
}
托管代码 (managed code):由公共语言运行库环境(而不是直接由操作系统)执行的代码。托管代码应用程序可以获得公共语言运行库服务,例如自动
垃圾回收、运行库类型检查和安全支持等。这些服务帮助提供独立于平台和语言的、统一的托管代码应用程序行为。
非托管代码(Unmanaged Code):在公共语言运行库环境的外部,由操作系统直接执行的代码。非托管代码必须提供自己的垃圾回收、类型检查、安全支
持等服务;它与托管代码不同,后者从公共语言运行库中获得这些服务。
Unsafe的代码介于这两者之间,它也是在CLR的环境中执行,但是我们可以直接操作内存。只要我们的代码包含下面三个指针操作符之一就需要使用Unsafe
关键字:
*
&
->
例如:
unsafe static void ChangeValue(int* pInt)
{
*pInt = 23;
}
上面的代码由于是在CLR下托管执行,为
了减少内存碎片C#的自动垃圾回收机制会允许已经分配的内存在运行时进行位置调整,所以如果我们多次调用的话就可能
导致指针指向其他的变量。比如*pInt为
指向一个变量的地址为1001,CLR在重新内存整理分配后该变量
就存储在地址为5001的地方。而原来1001的地方可能会
被分配其他变量,要解决这个问题我们就需要使用Fixed关键字。
fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。Fixed 还可用于创建固定大小的缓冲区。如下面例子:
using System;
class CaryData
{
public int data;
}
class CProgram
{
unsafe static void ChangeValue(int* pInt)
{
*pInt = 23; //3为这个指针的地址赋值23
}
public unsafe static void Main()
{
CaryData cd = new CaryData();
Console.WriteLine("改变前: {0}", cd.data);
fixed (int* p = &cd.data) // 1把整形的地址赋给了指针P
{
ChangeValue(p); //2专递指针
}
Console.WriteLine("改变后: {0}", cd.data); //4由于cd.data的和*p地址相同,所以cd.data 的输出是23
}
}
注意要勾选项目属性中生成标签的允许不安全代码。
T_Account ret;
unsafe
{
fixed (void * ptr = body)
{
ret = *((T_Account *)ptr); // 转换指针为(T_Account*),再获得指针的值也就是T_Account类型值
}
}
T_Account x = new T_Account ();
x.ID = 12;
x.Name = "thisistest" ;
x.Native_Currency = "USD" ;
byte [] message = new byte [T_Account .Size];
unsafe
{
void * ptr = &x; //将地址赋给这个指针
fixed (void * des = message) // 值赋给了这个地址
{
MemoryUtility .CopyData(des, ptr, T_Account .Size);
}
}
(*) unsafe 和 fixed
unsafe
{
int[] array = new int[10];
for (int i = 0; i < array.Length; i++)
{
array[i] =
i;
}
fixed (int* p = array)
{
for (int
i = 0; i < array.Length; i++)
{
System.Console.WriteLine(p[i]);
}
}
}
指针在c#中是不提倡使用的,有关指针的操作被认为是不安全的(unsafe)。因此运行这段代码之前,先要改一个地方,否则编译不过无法运行。
修
改方法:在右侧的solution
Explorer中找到你的项目,在项目图标(绿色)上点右键,选最后一项properties,然后在Build标签页里把Allow unsafe
code勾选上。之后这段代码就可以运行了,你会看到,上面这段代码可以像C语言那样用指针操纵数组。但前提是必须有fixed (int* p =
array),它的意思是让p固定指向数组array,不允许改动。因
为C#的自动垃圾回收机制会允许已经分配的内存在运行时进行位置调整,如果那样,p可能一开始指的是array,但后来array的位置被调整到别的位置
后,p指向的就不是array了。所以要加一个fixed关键字,把它定在那里一动不动,之后的操作才有保障。
另有两点需要注意:
1)指针的使用必须放在unsafe的区域里;unsafe关键字也可作为类或方法的修饰符。
2)fixed (int* p = array)中,p的定义不能写在别处,而且fixed关键字也只能在unsafe区域里使用。
(*) 略简洁的unsafe写法
class Program
{
unsafe public static UInt16
Htons(UInt16 src)
{
UInt16 dest;
// 不能照搬C的源代码,因为有些类型长度不一样,如char(2字节),long(8字节)
//
((char*)&dest)[0] = ((char*)&src)[1];
//
((char*)&dest)[1] = ((char*)&src)[0];
((byte*)&dest)[0] = ((byte*)&src)[1];
((byte*)&dest)[1] = ((byte*)&src)[0];
return
dest;
}
public static UInt16 ConciseHtons(UInt16 src)
{
UInt16 dest;
unsafe
{
((byte*)&dest)[0] = ((byte*)&src)[1];
((byte*)&dest)[1] = ((byte*)&src)[0];
}
return dest;
}
static void Main()
{
UInt16 val = 1;
// 如果方法是unsafe的,则必须在unsafe block里调用
unsafe
{
val = Htons(val);
}
Console.WriteLine(val);
// 更简洁的写法是把unsafe block写在函数内部
val =
ConciseHtons(val);
Console.WriteLine(val);
}
}
(*) stackalloc
stackalloc的用处仅仅是把数组分配在栈上(默认是分配在托管堆上的)。
class MyClass
{
public int val;
}
class Program
{
static void
Main()
{
unsafe
{
MyClass *p = stackalloc MyClass[1];
// Error!! 如果类型要放在托管堆上则不行,如果MyClass是struct就OK了
p->val = 1;
int *iArray = stackalloc int[100]; // OK,在栈上创建数组,
int类型本身就是放在栈上的
}
}
}
注意:指针指向的内存一定要固定。凡是C#里的引用类型(一切类型的 数组都是引用类型 )都是分配在托管堆上的,都不固定。有两种方法强制固定,一种是用stackalloc分配在栈上,另一种是用fixed 分配在堆上。