PInvoke各种总结(五、在C#中使⽤指针类型)
C#向开发⼈员隐藏了⼤部分基本内存管理操作,因为它使⽤了垃圾回收器和引⽤。但是,有时候我们也需要直接访问内存,例如:进⾏平台调⽤,性能优化等等。
.Net平台定义了两种主要数据类型:值类型和引⽤类型,其实还有第三种数据类型:指针类型。使⽤指针,可以绕开CLR的内存管理机制。(说明:在C#中使⽤指针,需要有相关C/C++指针操作基础)
1、C#中指针相关的操作符和关键字
操作符/关键字作⽤
*该操作符⽤于创建⼀个指针变量,和在C/C++中⼀样。也可⽤于指针间接寻址(解除引⽤)
&该操作符⽤于获取内存中变量的地址
->该操作符⽤于访问⼀个由指针表⽰的类型的字段,和在C++中⼀样
[]在不安全的上下⽂中,[]操作符允许我们索引由指针变量指向的位置
++,--在不安全的上下⽂中,递增和递减操作符可⽤于指针类型
+,-在不安全的上下⽂中,加减操作符可⽤于指针类型
==, !=, <, >, <=, >=在不安全的上下⽂中,⽐较和相等操作符可⽤于指针类型
stackalloc在不安全的上下⽂中,stackalloc关键字可⽤于直接在栈上分配C#数组,类似CRT中的_alloca函数
fixed在不安全的上下⽂中,fixed关键字可⽤于临时固定⼀个变量以使它的地址可被到
2、在C#中使⽤指针,需要启⽤“允许不安全代码”设置
选择项⽬属性->⽣成,钩上“允许不安全代码”
3、unsafe关键字
只有在unsafe所包含的代码区块中,才能使⽤指针。类似lock关键字的语法结构
除了声明代码块为不安全代码外,也可以直接构建“不安全的”结构、类型成员和函数。
1unsafe struct Point
2        {
3public int x;
4public int y;
5public Point* next;
6public Point* previous;
7        }
1unsafe static void CalcPoint(Point* point)
2        {
3//
4        }
也可以在导⼊⾮托管 DLL 的函数声明中使⽤unsafe
1  [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
2private static extern unsafe int memcpy(void* dest, void* src, int count);
注意:
指针不能指向引⽤或包含引⽤的结构,因为⽆法对对象引⽤进⾏垃圾回收,即使有指针指向它也是如此。垃圾回收器并不跟踪是否有任何类型的指针指向对象。
下⾯的⽰例代码可以说明:
///<summary>
///声明⼀个Point结构体
///</summary>
struct Point
{
public int x;
public int y;
}
static void Main(string[] args)
{
unsafe
{
//编译正常
Point p = new Point();
Point* pp = &p;
}
}
1//换成类
2class Point
3    {
4public int x;
5public int y;
6    }
4、*和&操作符
在不安全的上下⽂中,可以使⽤ * 操作符构建数据类型相对应的指针类型(指针类型、值类型和引⽤类型,⽰例代码中的type),使⽤ & 操作符获取被指向的内存地址。
1 type* identifier;
2void* identifier; //允许但不推荐
在同⼀个声明中声明多个指针时,星号 (*) 仅与基础类型⼀起写⼊;⽽不是⽤作每个指针名称的前缀。例如:
1int* p1, p2, p3;  // 正常
2int *p1, *p2, *p3;  // 错误
下⾯是使⽤*操作符进⾏指针类型声明
int* p p是指向整数的指针。
int** p p是指向整数的指针的指针。
int*[] p p是指向整数的指针的⼀维数组。
char* p p是指向字符的指针。
void* p p是指向未知类型的指针。
注意:
1、⽆法对void*类型的指针应⽤间接寻址运算符。但是,你可以使⽤强制转换将 void 指针转换为任何其他指针类型,反过来也是可以的。
2、指针类型不从object类继承,并且指针类型与object之间不存在转换。此外,装箱和取消装箱不⽀持指针。
下⾯的代码演⽰了如何声明指针类型:
1static void Main(string[] args)
2        {
3int []a = { 1, 2, 3, 4, 4 };
4
5unsafe
6            {
7//临时固定⼀个变量以使它的地址可被到
8fixed (int* p = &a[0])
9                {
10int* p2 = p;
11                    Console.WriteLine(*p2);
12                    p2++;
13                    Console.WriteLine(*p2);
14                    p2++;
15                    Console.WriteLine(*p2);
16                }
17            }
18
19        }
输出结果如下:
1
2
3
下⾯的代码演⽰了如何使⽤指针类型进⾏数据交换:
1static void Main(string[] args)
2        {
3int a = 1;
4int b = 2;
5
6unsafe
7            {
8                UnsafeSwap(&a, &b);
9            }
10
11            Console.WriteLine(a);
12            Console.WriteLine(b);
13        }
14
15///<summary>
16///使⽤指针
17///</summary>
18///<param name="a"></param>
19///<param name="b"></param>
20static unsafe void UnsafeSwap(int* a,int *b)
21        {
22int temp = *a;
23            *a = *b;
24            *b = temp;
25        }
26
27///<summary>
28///不使⽤指针的安全版本
29///</summary>
30///<param name="a"></param>
31///<param name="b"></param>
32static void SafeSwap(ref int a,ref int b)
33        {
34int temp = a;
35            a = b;
36            b = temp;
37        }
输出结果如下:
2
1
5、通过指针访问字段
定义如下结构体
1struct Point
2        {
3public int x;
4public int y;
5
6public override string ToString()
7            {
8return $"x:{x},y:{y}";
9            }
10        }
如果声明⼀个Point类型的指针,就需要使⽤指针字段访问操作符(->)来访问公共成员(和C++⼀样),也可以使⽤指针间接寻址操作符(*)来解除指针的引⽤,使其也可以使⽤ (.)操作符访问字段(和C++⼀样)。
1static unsafe void Main(string[] args)
2        {
3//通过指针访问成员
4            Point point = new Point();
5            Point* p = &point;
6            p->x = 10;
7            p->y = 5;
8            Console.WriteLine(p->ToString());
9
10//通过指针间接寻址访问成员
11            Point point2; //不使⽤ new 运算符的情况下对其进⾏实例化,需要在⾸次使⽤实例之前必须初始化所有实例字段。
12            Point* p2 = &point2;
13            (*p2).x = 128;
14            (*p2).y = 256;
15            Console.WriteLine((*p2).ToString());
16        }
运⾏结果如下:
x:10,y:5
x:128,y:256
6、stackalloc关键字
在不安全上下⽂中,可能需要声明⼀个直接从调⽤栈分配内存的本地变量(不受.Net垃圾回收器控制)。C#提供了与CRT函数_alloca等效的stackalloc关键字来满⾜这个需求。
1static unsafe void Main(string[] args)
2        {
3char* p = stackalloc char[3];
4
5for (int i = 0; i < 3; i++)
6            {
7                p[i] = (char)(i+65); //A-C
8            }
9
10            Console.WriteLine(*p);
11            Console.WriteLine(p[0]);
12
13            Console.WriteLine(*(++p));
14            Console.WriteLine(p[0]);
15
16            Console.WriteLine(*(++p));
17            Console.WriteLine(p[0]);
18        }
输出结果如下:
A
A
B
B
C
C
7、fixed关键字
在上⾯的⽰例中,我们可以看到,通过stackalloc关键字,在不安全上下⽂中分配⼀⼤块内存⾮常⽅便。但这块内存是在栈上的,当分配⽅法返回的时候,被分配的内存⽴即被清理。
假设有如下情况:
声明⼀个引⽤类型PointRef和⼀个值类型Point
1class PointRef
2        {
3public int x;
4public int y;
5
6public override string ToString()sizeof 指针
7            {
8return $"x:{x},y:{y}";
9            }
10        }
11
12struct Point
13        {
14public int x;
15public int y;
16
17public override string ToString()
18            {
19return $"x:{x},y:{y}";
20            }
21        }
调⽤者声明了⼀个PointRef类型的变量,内存将被分配在垃圾回收器堆上。如果⼀个不安全的上下⽂要与这个对象(或这个堆上的任何对
象)交互,就可能会出现问题,因为垃圾回收可随时发⽣。设想⼀下,恰好在清理堆的时候访问Point成员,这就很。
为了将不安全上下⽂中的引⽤类型变量固定,C#提供了fixed关键字,fixed语句设置指向托管类型的指针并在代码执⾏过程中固定该变量。换句说话:fixed关键字可以锁定内存中的引⽤变量。这样在语句的执⾏过程中,该变量地址保持不变。
事实上,也只有使⽤fixed关键字,C#编译器才允许指针指向托管变量。
1static unsafe void Main(string[] args)
2        {
3            PointRef pointRef = new PointRef();
4            Point point = new Point();
5
6int a = &pointRef.x;  //编译不通过
7
8int *b = &point.x;    //编译通过
9
10fixed(int *c = &pointRef.x)
11            {
12//编译通过
13            }
14        }
说明:
在fixed中初始化多个变量也是可以的
1//同时声明多个指针变量的语法跟C++中的不⼀样,需要注意
2fixed(int *e = &(pointRef.x) , f = &(pointRef.y) )
3            {
4
5            }
8、sizeof关键字
在不安全上下⽂中,sizeof关键字⽤于获取值类型(不是引⽤类型)的字节⼤⼩。sizeof可计算任何由System.ValueType派⽣实体的字节数。
1static void Main(string[] args)
2        {
3unsafe
4            {
5//不安全版本
6                Console.WriteLine(sizeof(int));
7                Console.WriteLine(sizeof(float));
8                Console.WriteLine(sizeof(Point));
9            }
10
11//安全版本
12            Console.WriteLine(Marshal.SizeOf(typeof(int)));
13            Console.WriteLine(Marshal.SizeOf(typeof(float)));
14            Console.WriteLine(Marshal.SizeOf(typeof(Point)));
15
16        }
9、避免使⽤指针
事实上在C#中,指针并不是新东西。因为在代码中可以⾃由使⽤引⽤,⽽引⽤就是⼀个类型安全的指
针。指针只是⼀个存储地址的变量,这和引⽤其实是⼀个原理。引⽤的主要作⽤是使C#更易于使⽤,防⽌⽤户⽆意中执⾏某些破坏内存中内容的操作。
使⽤指针后,可以进⾏低级的内存访问,但这是有代价的,使⽤指针的语法⽐引⽤类型的语法复杂得多,⽽且指针使⽤起来也⽐较困难,需要较⾼的编程技巧和强⼒。如果不仔细,就容易在程序中引⼊细微的,难以查的错误。另外,如果使⽤指针,就必须授予代码运⾏库的代码访问安全机制的⾼级别信任,否则就不能执⾏它。
MSDN上有如下关于指针的说明:
在公共语⾔运⾏时 (CLR) 中,不安全代码是指⽆法验证的代码。 C# 中的不安全代码不⼀定是危险的;只是 CLR ⽆法验证该代码的安全性。因此,CLR 将仅执⾏完全信任的程序集中的不安全代码。如果你使⽤不安全代码,你应该负责确保代码不会引发安全风险或指针错误。
⼤多数情况下,可以使⽤System.Intptr或ref关键字来替代指针完成我们想要的操作。
下⾯使⽤⽰例代码说明⼀下:(仅供演⽰)
这⾥还是以memcpy函数为例,假设我有⼀个Point结构的实例,要对这个Point进⾏拷贝。
声明Point结构
1struct Point
2    {
3public int x;
4public int y;
5    }
使⽤System.IntPtr:
1///<summary>
2///使⽤IntPtr
3///</summary>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。