.NET(C#) 中使用指针
在 .NET 中,C# 默认情况下是一种安全的、不支持直接使用指针的语言。然而,C# 支持在特定情况下使用不安全代码(即可以使用指针的代码)。使用不安全代码意味着放弃了 C# 和 .NET 运行时提供的一些安全保障,因此必须谨慎使用。不安全代码可能会增加程序出错的风险,特别是与内存管理相关的错误,如堆损坏、内存泄漏等。指针操作通常只在性能至关重要的情况下使用,或者在需要与系统级别或非托管代码互操作时使用。
参考文档:
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
方法,用于释放类所占用的非托管资源。这是一种确定性的清理方式,可以控制何时释放资源。
代码:
使用方法:
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 类型来定义安全函数指针对象。
参考文档: