1、 开启不安全代码支持
要在 C# 项目中使用指针,首先需要在项目设置中启用不安全代码。这通常在项目属性中设置。
1)Visual Studio 设置
在项目上右键属性,在点击 "生成" ,勾选允许不安代码,有些vs中那个选项可能是英文的,需要注意一下,如下图,
2)修改.csproj 文件
在 .csproj 文件中添加以下代码:
<PropertyGroup> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup>
2、使用 unsafe 关键字
在 C# 中,任何包含指针操作的代码块都需要被 unsafe
关键字标记。这可以是一个方法、一个代码块或整个类。
1)使用unsafe代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main(string[] args) { unsafe { int[] numbers = { 10, 20, 30, 40, 50 }; // 固定数组,防止垃圾回收器移动它,获取数组的指针 fixed (int* p = numbers) { // 输出数组中的每个元素 for (int i = 0; i < numbers.Length; i++) { Console.WriteLine("Element {0}: {1}", i, *(p + i)); } } } Console.ReadKey(); } } }
2)使用unsafe关键字
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication { class Program { // 定义一个标记了 unsafe 的方法 public unsafe static void UnsafeMethod() { int value = 10; int* pValue = &value; // 获取变量的地址 Console.WriteLine("Value before change: " + value); // 使用指针修改变量的值 *pValue = 20; Console.WriteLine("Value after change: " + value); } static void Main(string[] args) { // 调用 unsafe 方法 UnsafeMethod(); Console.ReadKey(); } } }
3、指针操作
声明指针使用 *
操作符声明指针变量,例如 int* p;
,获取变量地址:使用 &
操作符获取变量的地址,例如 p = &myVar;
。访问指针指向的值使用 *
操作符访问或修改指针指向的值,例如 *p = 5;
。
1)基础操作
使用不安全代码时,程序员负责确保代码的正确性和内存安全性。错误的指针操作可能会导致程序崩溃或数据损坏。fixed 语句限制了垃圾回收器对固定对象的处理,因此应谨慎使用,以免影响性能。
using System; using System.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main(string[] args) { //用指针操作栈上的值类型 int maxValue = 10; int* p = &maxValue; *p = 20; Console.WriteLine(p->ToString());eadKey(); } } //要用指针操作托管堆上的值类型,需要用到 fixed关键字 public unsafe class Coder { public int Age; public void SetAge(int age) { fixed (int* p = &Age) { *p = age; } } } }
2)分配释放内存
使用指针操作内存时,可以通过 Marshal 类来分配和释放非托管内存。这是因为在 .NET 中,内存管理通常是自动的,但在使用指针进行非托管内存操作时,需要手动处理内存的分配和释放。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main(string[] args) { // 分配非托管内存 int size = sizeof(int); IntPtr pMemory = System.Runtime.InteropServices.Marshal.AllocHGlobal(size); try { // 将 IntPtr 转换为指针 int* pInt = (int*)pMemory; // 在分配的内存中存储数据 *pInt = 123; // 读取内存中的数据 Console.WriteLine("Data: " + *pInt); } finally { // 释放非托管内存 System.Runtime.InteropServices.Marshal.FreeHGlobal(pMemory); } Console.ReadKey(); } } } System.Runtime.InteropServices.Marshal.FreeHGlobal(handle);
3)用IDispose接口管理内存
实现 IDisposable
接口允许你定义一个 Dispose
方法,用于释放类所占用的非托管资源。这是一种确定性的清理方式,可以控制何时释放资源。
public unsafe class UnmanagedMemory : IDisposable
{
public int Count { get; private set; }
private byte* Handle;
private bool _disposed = false;
public UnmanagedMemory(int bytes)
{
Handle = (byte*) System.Runtime.InteropServices.Marshal.AllocHGlobal(bytes);
Count = bytes;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
protected virtual void Dispose( bool isDisposing )
{
if (_disposed) return;
if (isDisposing)
{
if (Handle != null)
{
System.Runtime.InteropServices.Marshal.FreeHGlobal((IntPtr)Handle);
}
}
_disposed = true;
}
~UnmanagedMemory()
{
Dispose( false );
}
}
使用方法:
using (UnmanagedMemory memory = new UnmanagedMemory(10))
{
int* p = (int*)memory.Handle;
*p = 20;
Console.WriteLine(p->ToString());
}
4)使用stackalloc关键字
stackalloc
关键字用于在栈上分配内存块,通常用于分配小型数组。与在堆上分配内存(如使用 new
关键字)不同,stackalloc
分配的内存不需要垃圾回收,因此可以提高性能。但是,它只应该用于较小的内存分配,因为栈空间相对有限。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main(string[] args) { /// 分配一个大小为 10 的整数数组 int* array = stackalloc int[10]; // 初始化数组 for (int i = 0; i < 10; i++) { array[i] = i; } // 打印数组内容 for (int i = 0; i < 10; i++) { Console.WriteLine(array[i]); } } } }
4、C# 指针操作的几个缺点
在 C# 中使用指针操作(通常在 unsafe
代码块中进行)确实提供了更直接的内存访问和潜在的性能优势,但它也带来了一些明显的缺点和风险。
1)只能用来操作值类型
指针在 C# 中主要用于操作值类型(如 int, float, char 等)。.NET中,引用类型的内存管理全部是由GC管理的,无法取得其地址,因此,无法用指针来操作引用类型。
2)泛型不支持指针类型
泛型的一个主要优点是提供类型安全。但是,由于指针操作本质上是不安全的,因此 C# 不允许在泛型类或方法中使用指针类型。这意味着你不能创建一个泛型类或方法来处理通用的指针操作,限制了泛型的灵活
3)函数指针
C# 中有delegate,delegate 支持支持指针类型,lambda 表达式也支持指针。委托是 C# 中一个非常强大的功能,它允许将方法作为参数传递给其他方法,类似于 C 和 C++ 中的函数指针。委托可以引用具有特定参数列表和返回类型的任何方法。C# 提供 delegate 类型来定义安全函数指针对象。