前言
变量可以理解成是一块内存位置的别名,访问变量也就是访问对应内存中的数据。
指针是一种特殊的变量,它存储了一个内存地址,这个内存地址代表了另一块内存的位置。
指针指向的可以是一个变量、一个数组元素、一个对象实例、一块非托管内存、一个函数等。
截止到发文为止,.NET 最新正式版本为 .NET 9,C# 最新正式版本为 C# 13。文中提及的 IL 代码可能会随编译器版本的不同而有所差异,仅供参考。
本文将介绍到发文为止 C# 中的各类指针,并对比差异:
- 对象引用(Object Reference)
- 指针(Pointer,一些资料中称为非托管指针)
- IntPtr(表示指针或句柄的值,用于管理非托管资源或非托管代码交互)
- 函数指针(Function Pointer)
- 托管指针(Managed Pointer)
本文旨在为读者建立对各类指针的概念认知,不会每个细节都展开,读者可以参考 C# 的官方文档,了解更多用法。
涉及的知识点较多,如果存在纰漏和错误,还请谅解。
对象引用(Object Reference)
对象引用,也就是我们常说的引用类型变量,是一个类型安全的指针,指向引用类型实例的 MethodTable 指针,通过偏移和计算可以访问对象头和字段。
对象实例被分配在托管堆上,引用类型变量存储了一个指向该对象实例的引用。对象引用可以被赋值为 null,表示没有指向任何对象实例。通过 null 的对象引用访问不存在的对象会导致 NullReferenceException。
对象引用可以存在栈或者堆上,作为局部变量时,存储在栈上;作为值类型字段时,跟随值类型的位置存储;作为引用类型字段时,存储在堆上。
指针(Pointer)
指针的声明和使用#
指针允许用户直接操作内存地址,提供了更高的性能和灵活性,但也带来了更高的风险。因此,C# 只允许在用 unsafe 关键字标记的代码块中使用指针,并且需要在项目中启用 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>。
unsafe 关键字可以用于方法、代码块、字段、类、结构体等。
一些资料中将这边的指针(Pointer)称为非托管指针(Unmanaged Pointer),因为它们不受 GC 的管理。
我们需要使用 <type>* ptr 的语法来声明指针类型的变量。
通过 & 运算符获取变量的地址,通过 * 运算符访问指针指向的数据。