C#中的指针
在C#中,数据类型被划分为三类。pointer types,value types, 和reference types ,这是以它们在内存中存储值的方式为基础的。
简介
我们将其他类型的内存地址存储在一个指针型变量中。因为它缺乏一个单独的变量,所以引用型包含一个指向另一个存放数据的内存位置的指针。引用类型的例子有类、对象、数组、索引器、接口等。
我们明确地将数据类型包含在值数据形式内。整数、字符和浮点数,以及字母和数字,都是值数据类型的例子。
做一个指针式的声明
在C#中,我们声明指针,如下图所示。
type *variable_name;
其中* ,称为去引用管理员。去引用管理员或去引用操作符是用来从指针所指的地址中获取数值的。
考虑一下下面的例子。
public class Program
{
static unsafe void Main(string[] args)
{
int w = 76;
int* ptr = &w;
Console.WriteLine((int)ptr);
Console.WriteLine(*ptr);
}
}
w 是一个指针变量,可以保存 排序的位置。操作符 被称为引用操作符,它被用来获取变量地址。int &
变量w ,它可以被分配给一个指针变量,其内存地址由符号&w 指定。
如何运行不安全代码
让我们来学习如何在VS代码中允许使用不安全代码。
- 转到 "视图 "选项卡。然后从下拉菜单中选择解决方案资源管理器。
- 双击解决方案资源管理器中的 "属性 "选项来展开它。
- 勾选 "允许不安全代码 "选项。

安全代码和不安全代码
安全代码是在Common Language Runtime's supervision (CLR) 下运行的C#关键字,而不安全代码是在CLR的管理之外执行的C#关键字。
与使用指针的安全代码的C++和C编程语言不同,C#编程语言只允许使用不安全代码。
不安全代码可以作为修改器使用,或将一组语句标记为不安全。通用语言Runtime将安全代码翻译成软件指令,然后由计算机的CPU执行。
下面的例子使用了不安全代码。
using System;
namespace UnsafeCodeApplication
{
class Demo
{
public void Method()
{
unsafe
{
int m = 60,n=30;
int* ptr1 = &m, ptr2 = &n;
Console.WriteLine(*ptr1);
Console.WriteLine(*ptr2);
Console.WriteLine((int)ptr1);
Console.WriteLine((int)ptr2);
}
}
}
class Example
{
public static void Main()
{
Demo d = new Demo();
d.Method();
}
}
}
而输出的结果是:
60
30
1605887284
1605887280
输出的地址可能因机器而异,它是由你的计算机中的数值地址决定的。
有各种方法可以将语句执行为非托管,例如使用修改器或构造器。在上面的例子中,一个语句的集合已经被标记为不安全。
我们使用了两个变量,a 和b ,其值分别为60和30,指针包含它们的地址。然后我们显示了它们。
钉住一个对象
在C#中钉住一个对象需要限制一个对象进入垃圾收集器。
垃圾收集(GC)是CLR提供的服务之一,用于控制应用程序的内存分配和释放。它通过为操作分配一个相邻的地址空间区域(被称为非管理堆)来分配内存,并保留一个指向堆的下一个对象将被分配的地址的指针。
在管理堆上,引用类型被管理。
在执行完一个列表后,GC会释放不再使用的对象的内存,以解除对内存的分配。每个应用程序的根都被设置为null或者引用管理堆上的一个对象。GC可以访问JIT编译器和运行时所记录的活动根列表。
对于非托管资源,我们要明确地调用处置方法来从内存中删除对象。
比如说。
using System;
namespace UnsafeCodeApplication
{
class Demo
{
public unsafe static void Main()
{
int[] array = { 5, 6, 7, 8, 9 };
fixed (int* ptr = array)
for (int k=0; k< 5; k++)
{
Console.WriteLine("Value of array[{0}]={1}", k, *(ptr + k));
Console.WriteLine("Address of array[{0}]={1}", k, (int)(ptr + k));
}
Console.ReadKey();
}
}
}
而输出结果是。
Value of array[0]=5
Address of the array[0]=-773935792
Value of array[1]=6
Address of the array[1]=-773935788
Value of array[2]=7
Address of the array[2]=-773935784
Value of array[3]=8
Address of the array[3]=-773935780
Value of array[4]=9
Address of the array[4]=-773935776
我们使用fixed (int* ptr = array) ,将数组中的对象限制在一个固定的内存分配中。
指针和方法
在C#中,指针可以被传递,如下所示。
using System;
namespace UnsafeCodeApplication
{
class Demo
{
public unsafe void Method()
{
int x = 60,y = 30;
int* ptr1 = &x,ptr2 = &y;
Console.WriteLine(*ptr1);
Console.WriteLine(*ptr2);
Console.WriteLine((int)ptr1);
Console.WriteLine((int)ptr2);
}
}
class Example
{
public static void Main()
{
Demo d = new Demo();
d.Method();
}
}
}
输出结果是:
60
30
1748493636
1748493632
输出的地址可能因机器而异,它是由你的计算机中的数值地址决定的。
非管理代码与方法一起使用,该方法有两个变量x 和y ,其值分别为50和20。指针*ptr1 和*ptr2 指向其内存地址。
转换和指针
在C#中,指针类型不继承于对象,也没有办法将指针类型转换为对象。因此,指针不能帮助装箱和拆箱。在C#中支持不同指针类型之间的转换,以及指针类型和积分类型的转换。
C#在非管理的设置中坚持显式和隐式指针的变化。在隐式指针中,转换是从空类型到指针类型,也是从任何指针类型到类型void *类型。
对于显式指针,投掷运算符(())是必不可少的。
转换是在这个例子中。
- 指针的类型到其他形式的指针。
- 到其他指针类型:byte, sbyte, short, ushort, int, uint, long, ulong。
- 指向sbyte, byte, uint, int, long, ulong, short, ushort类型的指针。
让我们来做一个指针转换的例子。
char k = 'U';
char *pk = &k;
void *px = pk;
int *pj = (int *) px;
void *px = pk; 从指针类型到void *类型进行隐式转换。 ,使用转换操作符从指针类型到int类型进行显式转换。int *pj = (int *) px;
指针和数组
数组是一个类似数据类型的数据组合,只是通过它们在其中的位置来区分。
在C#程序中,指针符号被用来访问数组。
using System;
namespace UnsafeCodeApplication
{
class Demo
{
public unsafe static void Main()
{
int[] array = { 10, 20, 30, 40, 50 };
fixed (int* ptr = array)
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Value of array[{0}]={1}", i, *(ptr + i));
Console.WriteLine("Address of array[{0}]={1}", i, (int)(ptr + i));
}
Console.ReadKey();
}
}
}
而输出的结果是。
Value of array[0]=10
Address of the array[0]=-521514320
Value of array[1]=20
Address of the array[1]=-5215143224
Value of array[2]=30
Address of the array[2]=-521514328
Value of array[3]=40
Address of the array[3]=-521514332
Value of array[4]=50
Address of the array[4]=-521514336
输出的地址可能因机器而异,它是由你的计算机中的数值地址决定的。
上面的代码包含非管理语句。我们声明了一个有五个元素的数组,并使用Console.Writeline() 来显示数组的内存地址和值数据类型。
我们之前曾讨论过对象的钉住问题,我们将数组钉住在一个固定的内存分配上。上述代码的输出将同时包含数组中的每个元素和它的地址。
指针和结构
在C#中,结构只由值类型组成。指针只应在以值类型为主要成员的系统中使用。
比如说
using System;
namespace UnsafeCodeApplication
{
struct student
{
public int studentID;
public double fees;
public student(int a, double b)
{
studentID = a;
fees = b;
}
};
class Program
{
static void Main(string[] args)
{
unsafe
{
student A1 = new student(005, 45000), A2 = new student(006, 43333);
student* A1_ptr = &A1, A2_ptr = &A2;
Console.WriteLine("Student details 1");
Console.WriteLine("student ID: {0} Fees: {1}",
A1_ptr->studentID, A1_ptr->fees);
Console.WriteLine("Student datails 2");
Console.WriteLine("student ID: {0} Fees: {1}",
A2_ptr->studentID, A2_ptr->fees);
}
}
}
}
输出的结果是。
Students details 1
Student ID: 5 Fees: 45000
Students details 2
Student ID: 6 Fees: 43333
结构student ,用学生的ID和费用配置构建器来初始化值。指针表示包含原始值型而不是引用型结构。
有两个主要的方法变量用于学生和费用的指针,用A1 和A2 地址初始化。Console.writeline()被用来显示学生的详细信息和费用。
结论
正如我们所发现的那样,指针显示内存地址并执行非托管代码。使用不安全语句背后的原因是,垃圾收集器在非管理环境中不跟踪内存地址。指针在队列和堆栈数据类型中应用。