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 类型来定义安全函数指针对象。