在C#中使用指针

news2024/11/30 0:40:42

C#向开发人员隐藏了大部分基本内存管理操作,因为它使用了垃圾回收器和引用。但是,有时候我们也需要直接访问内存,例如:进行平台调用,性能优化等等。

.Net平台定义了两种主要数据类型:值类型和引用类型,其实还有第三种数据类型:指针类型。使用指针,可以绕开CLR的内存管理机制。(说明:在C#中使用指针,需要有相关C/C++指针操作基础)

1、C#中指针相关的操作符和关键字

操作符/关键字作用
*该操作符用于创建一个指针变量,和在C/C++中一样。也可用于指针间接寻址(解除引用)
&该操作符用于获取内存中变量的地址
->该操作符用于访问一个由指针表示的类型的字段,和在C++中一样
[]在不安全的上下文中,[]操作符允许我们索引由指针变量指向的位置
++,--在不安全的上下文中,递增和递减操作符可用于指针类型
+,-在不安全的上下文中,加减操作符可用于指针类型
==, !=, <, >, <=, >=在不安全的上下文中,比较和相等操作符可用于指针类型
stackalloc在不安全的上下文中,stackalloc关键字可用于直接在栈上分配C#数组,类似CRT中的_alloca函数 
 fixed在不安全的上下文中,fixed关键字可用于临时固定一个变量以使它的地址可被找到

 2、在C#中使用指针,需要启用“允许不安全代码”设置

选择项目属性->生成,钩上“允许不安全代码”

3、unsafe关键字

只有在unsafe所包含的代码区块中,才能使用指针。类似lock关键字的语法结构

除了声明代码块为不安全代码外,也可以直接构建“不安全的”结构、类型成员和函数。

1   unsafe struct Point
2         {
3             public int x;
4             public int y;
5             public Point* next;
6             public Point* previous;
7         }

1    unsafe static void CalcPoint(Point* point)
2         {
3             //
4         }

 也可以在导入非托管 DLL 的函数声明中使用unsafe

1  [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
2  private 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  //换成类
2  class Point
3     {
4         public int x;
5         public int y;      
6     }

 4、*和&操作符

在不安全的上下文中,可以使用 操作符构建数据类型相对应的指针类型(指针类型、值类型和引用类型,示例代码中的type),使用 操作符获取被指向的内存地址。

1 type* identifier;
2 void* identifier; //允许但不推荐

在同一个声明中声明多个指针时,星号 (*) 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:

1 int* p1, p2, p3;   // 正常
2 int *p1, *p2, *p3;   // 错误

 下面是使用*操作符进行指针类型声明

int* pp 是指向整数的指针。
int** pp 是指向整数的指针的指针。
int*[] pp 是指向整数的指针的一维数组。
char* pp 是指向字符的指针。
void* pp 是指向未知类型的指针。

注意:

1、无法对 void* 类型的指针应用间接寻址运算符。 但是,你可以使用强制转换将 void 指针转换为任何其他指针类型,反过来也是可以的。

2、指针类型不从object类继承,并且指针类型与 object 之间不存在转换。 此外,装箱和取消装箱不支持指针。 

下面的代码演示了如何声明指针类型:

 1 static void Main(string[] args)
 2         {
 3             int []a = { 1, 2, 3, 4, 4 };
 4 
 5             unsafe
 6             {
 7                 //临时固定一个变量以使它的地址可被找到
 8                 fixed (int* p = &a[0])
 9                 {
10                     int* 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

下面的代码演示了如何使用指针类型进行数据交换:

 1 static void Main(string[] args)
 2         {
 3             int a = 1;
 4             int b = 2;
 5 
 6             unsafe
 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>
20         static unsafe void UnsafeSwap(int* a,int *b)
21         {
22             int 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>
32         static void SafeSwap(ref int a,ref int b)
33         {
34             int temp = a;
35             a = b;
36             b = temp;
37         }

输出结果如下:

2

1

5、通过指针访问字段

定义如下结构体

 1  struct Point
 2         {
 3             public int x;
 4             public int y;
 5 
 6             public override string ToString()
 7             {
 8                 return $"x:{x},y:{y}";
 9             }
10         }

如果声明一个Point类型的指针,就需要使用指针字段访问操作符(->)来访问公共成员(和C++一样),也可以使用指针间接寻址操作符(*)来解除指针的引用,使其也可以使用 (.)操作符访问字段(和C++一样)。

 1 static 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关键字来满足这个需求。

 1 static unsafe void Main(string[] args)
 2         {
 3             char* p = stackalloc char[3];
 4 
 5             for (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

 1     class PointRef
 2         {
 3             public int x;
 4             public int y;
 5 
 6             public override string ToString()
 7             {
 8                 return $"x:{x},y:{y}";
 9             }
10         }
11 
12         struct Point
13         {
14             public int x;
15             public int y;
16 
17             public override string ToString()
18             {
19                 return $"x:{x},y:{y}";
20             }
21         }

调用者声明了一个PointRef类型的变量,内存将被分配在垃圾回收器堆上。如果一个不安全的上下文要与这个对象(或这个堆上的任何对象)交互,就可能会出现问题,因为垃圾回收可随时发生。设想一下,恰好在清理堆的时候访问Point成员,这就很

为了将不安全上下文中的引用类型变量固定,C#提供了fixed关键字,fixed语句设置指向托管类型的指针并在代码执行过程中固定该变量。换句说话:fixed关键字可以锁定内存中的引用变量。这样在语句的执行过程中,该变量地址保持不变。

事实上,也只有使用fixed关键字,C#编译器才允许指针指向托管变量。

 1 static unsafe void Main(string[] args)
 2         {
 3             PointRef pointRef = new PointRef();
 4             Point point = new Point();
 5 
 6             int a = &pointRef.x;  //编译不通过
 7 
 8             int *b = &point.x;    //编译通过
 9 
10             fixed(int *c = &pointRef.x)
11             {
12                 //编译通过
13             }
14         }

 说明:

在fixed中初始化多个变量也是可以的

1             //同时声明多个指针变量的语法跟C++中的不一样,需要注意
2             fixed(int *e = &(pointRef.x) , f = &(pointRef.y) )
3             {
4 
5             }

8、sizeof关键字

在不安全上下文中,sizeof关键字用于获取值类型(不是引用类型)的字节大小。sizeof可计算任何由System.ValueType派生实体的字节数。

 1    static  void Main(string[] args)
 2         {
 3             unsafe
 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结构

1 struct Point
2     {
3         public int x;
4         public int y;
5     }

使用System.IntPtr:

1         /// <summary>
2         /// 使用IntPtr
3         /// </summary>
4         /// <param name="pDst"></param>
5         /// <param name="pSrc"></param>
6         /// <param name="count"></param>
7         /// <returns></returns>
8         [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
9         private static extern unsafe int memcpyi(IntPtr pDst, IntPtr pSrc, int count);

 1         static void MemCpyIntPtr()
 2         {
 3             var p = new Point() { x = 200,y = 10};
 4             Console.WriteLine(p.x + " " + p.y);
 5 
 6             var size = Marshal.SizeOf(p);
 7             
 8             IntPtr ptrSrc = Marshal.AllocHGlobal(size);
 9             IntPtr ptrDest = Marshal.AllocHGlobal(size);
10 
11             //将结构体Point转换成ptrSrc
12             Marshal.StructureToPtr(p, ptrSrc, false);
13 
14             //memcpy
15             memcpyi(ptrDest, ptrSrc, size);
16 
17             //再转换成结构体
18             Point p2 = new Point();
19             //先输出一次进行对比
20             Console.WriteLine(p2.x + " " + p2.y);
21 
22             p2 = (Point)Marshal.PtrToStructure(ptrDest, typeof(Point));
23             Console.WriteLine(p2.x + " " + p2.y);
24 
25         }

运行结果如下:

200 10
0 0
200 10

使用指针:

1         /// <summary>
2         /// 使用指针
3         /// </summary>
4         /// <param name="pDst"></param>
5         /// <param name="pSrc"></param>
6         /// <param name="count"></param>
7         /// <returns></returns>
8         [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)]
9         private static extern unsafe int memcpyp(void* pDst, void* pSrc, int count);

 1         static unsafe void MemCpyPointer()
 2         {
 3             Point p = new Point() { x = 200, y = 10 };
 4             Point p2 = new Point();
 5 
 6             Console.WriteLine(p.x + " " + p.y);
 7             Console.WriteLine(p2.x + " " + p2.y);
 8 
 9             Point* pSrc = &p;
10             Point* pDest = &p2;
11 
12             memcpyp((void*)pDest, (void*)pSrc, sizeof(Point));
13 
14             p2 = *pDest;
15             Console.WriteLine(p2.x + " " + p2.y);
16         }

运行结果如下:

200 10
0 0
200 10

下面介绍使用指针传递时的另外一种情况,这种情况我们可以使用ref来代替指针完成操作。

先用C++封装一个库,导出如下函数,用来打印一个整形数组

 1 extern "C" __declspec(dllexport) void PrintArray(int* pa,int size);
 2 
 3 
 4 extern "C" __declspec(dllexport) void PrintArray(int* pa,int size)
 5 {
 6     for (size_t i = 0; i < size; i++)
 7     {
 8         std::cout << *pa << std::endl;
 9         pa++;
10     }
11 }

使用ref:

1 [DllImport("demo_lib.dll",EntryPoint = "PrintArray")]
2 private static extern void PrintArrayRef(ref int pa,int size);

1         static void PrintArrayRef()
2         {
3             int[] array = new int[] { 1,2,3};
4 
5             //使用ref关键字传的是引用,ref[0]其实就是传的首地址
6             PrintArrayRef(ref array[0], array.Length);
7         }

运行结果:

1
2
3

使用指针:

1         [DllImport("demo_lib.dll", EntryPoint = "PrintArray")]
2         private static extern unsafe void PrintArrayPointer(int* pa, int size);

 1         static unsafe void PrintArrayPointer()
 2         {
 3             int size = 3;
 4             int* array = stackalloc int[3];
 5 
 6             for (int i = 0; i < size; i++)
 7             {
 8                 array[i] = i+1;
 9             }
10             PrintArrayPointer(array, size);
11         }

运行结果:

1
2
3

以上的示例程序可以在这里下载

参考资料:

Unsafe code - C# language specification | Microsoft Learn

不安全代码、数据指针和函数指针 - C# reference | Microsoft Learn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2227069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端零基础入门到上班:【Day2】开发环境VSCode安装

VSCode 安装教程&#xff1a;图文保姆教程 引言 在前端开发中&#xff0c;选择合适的代码编辑器是提高工作效率的重要一步。Visual Studio Code&#xff08;简称 VSCode&#xff09;作为一款强大的开源编辑器&#xff0c;因其简洁易用、功能强大、扩展性好而广受开发者喜爱。…

MES系列- 统计过程分析(SPC)实现

MES系列文章目录 ISA-95制造业中企业和控制系统的集成的国际标准-(1) ISA-95制造业中企业和控制系统的集成的国际标准-(2) ISA-95制造业中企业和控制系统的集成的国际标准-(3) ISA-95制造业中企业和控制系统的集成的国际标准-(4) ISA-95制造业中企业和控制系统的集成的国际标准…

面对复杂的软件需求:5大关键策略!

面对软件需求来源和场景的复杂性&#xff0c;有效地管理和处理需求资料是确保项目成功的关键&#xff0c;能够提高需求理解的准确性&#xff0c;增强团队协作和沟通&#xff0c;降低项目风险&#xff0c;提高开发效率。反之&#xff0c;项目可能面临需求理解不准确、团队沟通不…

react 基础学习笔记

1.react 语法 ①数据渲染 函数组件将HTML结构直接写在函数的返回值中 JSX只能有一个根元素 JSX插值写法 插值可以使用的位置 1.标签内容&#xff1b; 2.标签属性 JSX 条件渲染&#xff1a;三目运算符&#xff1b; JSX根据数据进行列表渲染&#xff1a;map()方法&#x…

Elastic Stack - FileBeat 入门浅体验

Filebeat 是 Elastic Stack 中的一个轻量级日志转发器&#xff0c;主要用于收集和转发日志数据。Filebeat 作为代理安装在您的服务器上&#xff0c;可以监控您指定的日志文件或位置&#xff0c;收集日志事件&#xff0c;并将其转发到 Elasticsearch 或 Logstash 进行索引。 一…

XCode16中c++头文件找不到解决办法

XCode16中新建Framework&#xff0c;写完自己的c代码后&#xff0c;提示“<string> file not found”等诸如此类找不到c头文件的错误。 工程结构如下&#xff1a; App是测试应用&#xff0c;BoostMath是Framework。基本结构可以参考官方demo&#xff1a;Mix Swift and …

“循环购体系:创新消费回馈模式引领电商新风尚“

各位听众&#xff0c;你们好&#xff0c;我是吴军&#xff0c;今天我想与你们分享一种创新且引人注目的商业模式——循环购体系。这是一种融合了消费回馈与积分制度的新型购物模式&#xff0c;它在顾客与商家之间搭建了一个全新的、互动性强的桥梁。 在循环购体系的运作中&…

云联网对等连接--实现内网互通

云联网 今天给大家介绍一款产品&#xff0c;腾讯云的云联网。 云联网&#xff1a;为您提供云上私有网络间&#xff08;VPC&#xff09;、VPC 与本地数据中心间&#xff08;IDC&#xff09;内网互联的服务&#xff0c;具备全网多点互联、路由自学习、链路选优及故障快速收敛等…

详细解读 CVPR2024:VideoBooth: Diffusion-based Video Generation with Image Prompts

Diffusion Models专栏文章汇总:入门与实战 前言:今天是程序员节,先祝大家节日快乐!文本驱动的视频生成正在迅速取得进展。然而,仅仅使用文本提示并不足以准确反映用户意图,特别是对于定制内容的创建。个性化图片领域已经非常成功了,但是在视频个性化领域才刚刚起步,这篇…

构建自然灾害预警决策一体化平台,筑牢工程安全数字防线

近年来&#xff0c;国家和部委也强调了要切实加强地质灾害监测预警。作为国内智慧应急领域的先行者&#xff0c;Mapmost持续探索利用数字孪生技术&#xff0c;推进自然灾害风险预警精细化&#xff0c;强化对监测数据的综合分析和异常信息研判处置。建立健全区域风险预警与隐患点…

.NET Core WebApi第7讲:项目的发布与部署

一、理解 二、项目的发布与部署 1、点击Publish进行发布 2、等待生成publish文件&#xff0c;如下图 3、把上图中发布的文件在服务器里面装上&#xff0c;即在windows的IIS里把它挂上去。如此便可以直接去访问当前的前/后端了。 &#xff08;1&#xff09; 注意&#xff1a;…

Python自动化测试中的Mock与单元测试实战

在软件开发过程中&#xff0c;自动化测试是确保代码质量和稳定性的关键一环。而Python作为一门灵活且强大的编程语言&#xff0c;提供了丰富的工具和库来支持自动化测试。本文将深入探讨如何结合Mock与单元测试&#xff0c;利用Python进行自动化测试&#xff0c;以提高代码的可…

前端获取csv或者excel 静态数据并使用

这里我将空格全部替换成了 || 好让我变成数组&#xff0c;从而拿到每一条数据中的第一项&#xff0c;相当于excel或者csv文件的第一列的东西 axios.get("/csv/zhongxiang").then((res) > {let rows res.data.split("\n");for (let row of rows) {let c…

Axios 请求超时设置无效的问题及解决方案

文章目录 Axios 请求超时设置无效的问题及解决方案1. 引言2. 理解 Axios 的超时机制2.1 Axios 超时的工作原理2.2 超时错误的处理 3. Axios 请求超时设置无效的常见原因3.1 配置错误或遗漏3.2 超时发生在建立连接之前3.3 使用了不支持的传输协议3.4 代理服务器或中间件干扰3.5 …

Windows 11 24H2:阻碍新更新的硬件和软件

由于 Microsoft 对特定设备和软件配置采取了保护措施或兼容性限制&#xff0c;数千名用户无法使用 Windows 11 24H2。 微软对使用可能与 Windows 11 24H2 冲突的硬件或应用程序的特定设备设置兼容性限制&#xff0c;从而导致崩溃、性能问题、死机或其他异常行为。 这些限制将…

HelloCTF [RCE-labs] Level 6 - 通配符匹配绕过

开启靶场&#xff0c;打开链接&#xff1a; GET传参cmd /[b-zA-Z_#%^&*:{}\-\<>\"|;\[\]]/ b-zA-Z 过滤b到Z范围内的任何单个字符 _ 过滤下划线 :{}\-\<>\"| 匹配这些符号之一 ;\[\] 匹配这些符号之一 可以尝试在Linux终端中做下面的几个实验&a…

VLAN(虚拟局域网)详解:概念、原理与特点

VLAN&#xff08;虚拟局域网&#xff09;详解&#xff1a;概念、原理与特点 在现代网络中&#xff0c;尤其是企业级网络环境中&#xff0c;VLAN&#xff08;虚拟局域网&#xff09;成为一种非常重要的技术。它不仅可以提升网络的管理效率&#xff0c;还能够有效地隔离不同的设…

python机器人编程——一种3D骨架动画逆解算法的启示(上)

目录 一、前言二、fabrik 算法三、python实现结论PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源ps3.移动小车相关文章资源ps3.wifi小车控制相关文章资源 一、前言 我们用blender等3D动画软件时&#xff0c;会用到骨骼的动画&#xff0c;通过逆向IK动力学…

docker上传离线镜像包到Artifactory

docker上传离线镜像包到Artifactory 原创 大阳 北京晓数神州科技有限公司 2024年10月25日 17:33 北京 随着docker官方源的封禁&#xff0c;最近国内资源也出现无法拉取的问题&#xff0c;Artifactory在生产环境中&#xff0c;很少挂外网代理去官方源拉取&#xff0c;小编提供…

await前后线程切换改变,AsyncLocal<T>比ThreadLocal<T> 更适合多线程变量隔离的场景

1. await前后线程发生切换&#xff0c;不一定保留在原线程中执行&#xff1b; 2. AsyncLocal<T> 比 ThreadLocal<T> 更适合多数多线程变量隔离的场景。 从 ThreadLocal 到 AsyncLocal https://cloud.tencent.cn/developer/article/1902826