unity的C#学习——不安全代码(声明不安全代码块:实现C/C++指针的创建与相关操作)

这篇具有很好参考价值的文章主要介绍了unity的C#学习——不安全代码(声明不安全代码块:实现C/C++指针的创建与相关操作)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


C# 不安全代码

不安全代码(Unsafe code)是指使用了指针变量的代码块,或者执行需要访问非托管代码(unmanaged code)的操作指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。在C#中,不安全代码必须使用 unsafe 修饰符标记,并需要在编译时启用 AllowUnsafeCode 选项。

为了编译不安全代码,您必须切换到命令行编译器指定 /unsafe 命令行。
例如,为了编译包含不安全代码的名为 prog1.cs 的程序,需在命令行中输入命令:

csc /unsafe prog1.cs

要在 Visual Studio 中启用 AllowUnsafeCode,请按照以下步骤操作:

  1. 打开你的 C# 项目。
  2. 右键单击项目并选择“属性”。
  3. 选择“建构”选项卡。
  4. 在“高级”下拉菜单中,将“允许不安全代码”选项设置为“是”。
  5. 单击“确定”保存更改。

不安全代码通常用于以下几个方面:

  • 调用非托管代码(例如使用DllImport调用C++ DLL)
  • 实现某些底层功能,例如指针操作和内存管理
  • 对于性能关键的代码,例如高性能算法和图形渲染。

1、不安全代码的基本语法

不安全代码的基本语法主要涉及 unsafe 关键字和指针变量的声明和使用:

1.1 声明不安全代码块

使用 unsafe 关键字声明不安全代码块,例如:

unsafe
{
    // 不安全的代码
}

1.2 声明指针变量

使用 * 运算符声明指针变量,例如:

int* ptr;

这里的 ptr 是指向 int 类型变量的指针。下标展示了常见指针类型声明的实例:

实例 描述
int* p p 是指向整数的指针。
double* p p 是指向双精度数的指针。
float* p p 是指向浮点数的指针。
int** p p 是指向整数的指针的指针。
int*[] p p 是指向整数的指针的一维数组。
char* p p 是指向字符的指针。
void* p p 是指向未知类型的指针。

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

int* p1, p2, p3;     // 正确  
int *p1, *p2, *p3;   // 错误 

1.3 操作指针变量

变量使用 & 运算符,可以获取变量的地址,例如:

int x = 10;
int* ptr = &x;

这里的 ptr 指向 x 的地址。

指针使用 * 运算符,可以访问指针所指向的变量的值(通常称之为“解引用指针”),例如:

int y = *ptr;

这里的 y 等于 x 的值。

两个运算符也可以结合使用,从而创建一个指向相同变量的指针,下面是一个示例代码:

unsafe 
{
    int value = 10;
    int* ptr = &value;  // 定义指针变量
    int* ptr2 = &(*ptr);  // 获取指针ptr的地址,并定义新指针ptr2
}

2、不安全代码的类型转换

在 C# 中,指针和引用类型之间的类型转换需要使用强制类型转换。可以使用 (type)expression 的语法进行强制类型转换,其中 type 是要转换为的类型,而 expression 是要转换的表达式。

例如,将 int* 转换为 long*,可以使用以下语法:

int* ptr = ...;
long* lptr = (long*)ptr;

此外还可以使用类型转换方法,详见上面链接的C#数据类型转换部分。

2.1 错误用法示例

需要注意的是,进行指针类型转换时需要非常小心,因为指针类型的转换很容易导致不安全的内存访问。下面是一个 int 类型的指针转换为 float 类型的指针,然后通过指针修改 float 类型的值的示例代码:

unsafe static void Main(string[] args)
{
    int intValue = 10;

    // 创建一个指向 int 变量的指针
    int* intPtr = &intValue;

    // 将 int 类型的指针转换为 float 类型的指针
    float* floatPtr = (float*)intPtr;

    // 通过 float 类型的指针修改值
    *floatPtr = 20.5f;

    Console.WriteLine("intValue = {0}", intValue);  // 输出:intValue = 1092616192
    Console.WriteLine("*floatPtr = {0}", *floatPtr);  // 输出:*floatPtr = 20.5
}

上述代码中通过 float 类型的指针修改了 intValue 变量的内存,这是一种类型不匹配的类型转换,导致内存错误,intValue 的值变为一串混乱的数字,我们应该避免这样的操作。

2.2 正确用法示例

下面的实例演示了正确的类型转换用法,使用到 ToString 类型转换方式,和强制转换符 (int)

using System;
namespace UnsafeCodeApplication
{
   class Program
   {
      public static void Main()
      {
         unsafe
         {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} " , var);
            Console.WriteLine("Data is: {0} " , p->ToString());
            Console.WriteLine("Address is: {0} " , (int)p);
         }
         Console.ReadKey();
      }
   }
}

上面使用了不安全代码来创建一个指向 int 类型变量的指针,并使用 -> 运算符访问该指针所指向的变量,并使用 ToString() 方法将其转换为字符串输出。同时,它还将变量地址强转为 int 类型后打印。

  • -> 运算符用于访问通过指针间接引用的结构体或类的成员。它是一个简便的语法,等同于用 * 运算符访问指针,然后再用 . 运算符访问成员。
  • 例如,对于一个指向结构体的指针 ptr,可以使用 ptr->member 来访问结构体中的成员 member,这等同于使用 (*ptr).member
  • 所以上述代码相当于访问p指针指向 int 类型的实例,再访问该实例的 ToString() 方法,等同语使用 (*p).ToString()

当上面的代码被编译和执行时,它会产生下列结果:

Data is: 20
Data is: 20
Address is: 77128984


3、固定托管对象的地址

使用 fixed 关键字创建指向托管对象(如数组)的指针,并在关键字的作用域内固定托管对象的(首)地址为指针所指的内存位置,以确保 GC 不会移动该托管对象。例如:

unsafe
{
    int[] arr = new int[10];
    fixed (int* p = arr)
    {
        // 操作指向数组的指针 p
    }
}

这里的 p 指向数组 arr 的首地址,且该数组地址在 fixed 代码块的作用域内固定,不会被 GC 移动。

GC是垃圾回收(Garbage Collection)的缩写,是指计算机程序运行时,自动检测和回收不再使用的内存资源的机制。

在固定了数组的内存地址后,我们就可以通过指针操作数组的元素,下面的实例演示了这点:

using System;

namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe static void Main()
      {
         int[] list = {10, 100, 200};

         // 使用fixed关键字创建指向list数组的指针
         fixed (int* ptr = list)
         {
            /* 显示指针中数组地址 */
            for (int i = 0; i < 3; i++)
            {
               // 打印第i个元素的地址
               Console.WriteLine("Address of list[{0}]={1}", i, (int)(ptr + i));

               // 打印第i个元素的值
               Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
            }
         }

         Console.ReadKey();
      }
   }
}

在上述代码中,数组名称 int[] list指向数组的指针 int *p 是不同的变量类型

  • 我们可以增加指针变量 p,因为它在内存中不是固定的;
  • 但是数组首地址在内存被固定,所以我们不能增加变量 list

当上面的代码被编译和执行时,它会产生下列结果:

Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200


4、指针作为方法的参数

指针可以作为方法的参数,使得方法可以直接修改指向内存位置的数据(效果等同于引用传参)。但要注意的是,如果方法的参数中包含指针类型,那么在方法声明中需要添加 unsafe 关键字,表示该方法包含不安全代码。

  • 在声明了 unsafe 的方法内部,就不需要再声明 unsafe 代码块了,因为方法的作用域已经被标记为不安全,可以直接使用指针等不安全的操作

下面是一个实例代码:

using System;

namespace UnsafeCodeApplication
{
    class TestPointer
    {
        // swap 方法:交换两个整型指针所指向的变量的值
        public unsafe void swap(int* p, int *q)
        {
            int temp = *p; // 用临时变量保存 p 指针所指向的值
            *p = *q;       // 用 q 指针所指向的值更新 p 指针所指向的值
            *q = temp;     // 用临时变量中的值更新 q 指针所指向的值
        }

        public unsafe static void Main()
        {
            TestPointer p = new TestPointer();
            int var1 = 10;   // 定义整型变量 var1,并赋值为 10
            int var2 = 20;   // 定义整型变量 var2,并赋值为 20
            int* x = &var1;  // 定义指向 var1 变量的指针 x
            int* y = &var2;  // 定义指向 var2 变量的指针 y

            // 输出变量交换前的值
            Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);

            // 调用 swap 方法,将 x 和 y 作为参数传入
            p.swap(x, y);

            // 输出变量交换后的值
            Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);

            Console.ReadKey();
        }
    }
}

当上面的代码被编译和执行时,它会产生下列结果:

Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10


5、不安全代码的安全性问题

不安全代码具有以下安全性问题:

  • 内存访问越界:不安全代码使用指针访问内存,如果指针指向的内存区域超出了程序分配的内存区域范围,就会出现内存访问越界的问题。内存访问越界可能会导致程序崩溃、数据损坏等问题。

  • 空指针引用:在不安全代码中,指针可能为 null,这就意味着指针指向的内存地址是无效的。如果程序尝试访问空指针所指向的内存区域,就会出现空指针引用的问题。

  • 内存泄漏:在不安全代码中,程序需要手动分配和释放内存,如果程序忘记释放内存,就会出现内存泄漏的问题。内存泄漏可能会导致程序占用过多的内存,最终导致系统崩溃或变慢。

  • 缓冲区溢出:在不安全代码中,程序使用指针访问数组或缓冲区时,如果程序没有对数组或缓冲区的长度进行检查,就可能会出现缓冲区溢出的问题。缓冲区溢出可能会导致程序崩溃、数据损坏或安全漏洞。

因此,在编写和使用不安全代码时,应该非常小心,确保代码的正确性和安全性。可以使用代码静态分析工具或手动代码审查等方式来减少不安全代码的风险。此外,不应该滥用不安全代码,应该尽可能使用安全的代码编写方式来避免潜在的安全问题。


6、使用不安全代码实现高性能算法

使用不安全代码可以实现高性能算法,因为它可以直接访问和修改内存中的数据,而不需要经过语言的类型检查和其他安全性检查。这使得不安全代码可以更快地执行,因为它可以避免一些额外的开销。下面是一些使用不安全代码实现高性能算法的例子:

  • 图像处理算法:在图像处理中,需要处理大量的像素数据。使用不安全代码可以直接访问像素数据,从而实现更快的图像处理算法。

  • 数组操作算法:在某些算法中,需要频繁地访问数组元素。使用不安全代码可以直接访问数组元素,避免了数组边界检查等开销,从而实现更快的数组操作算法。

  • 高精度计算算法:在某些高精度计算算法中,需要频繁地进行位操作和指针操作。使用不安全代码可以更方便地进行这些操作,从而实现更快的高精度计算算法。

需要注意的是,使用不安全代码需要谨慎,因为它可能会导致内存泄漏和其他安全问题。在编写不安全代码时,需要确保代码的正确性和安全性,以避免出现意外的问题。文章来源地址https://www.toymoban.com/news/detail-706950.html

到了这里,关于unity的C#学习——不安全代码(声明不安全代码块:实现C/C++指针的创建与相关操作)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 1.Unity中c#代码学习(读取物体名称+位置+移动)

    Ctrl + K + C 批量注释 Ctrl + K + U 批量取消注释 Debug.Log(\\\"**\\\");输出“**”中内容 GameObject obj = this.gameObject; this表示当前指代的物品 GameObject表示游戏中物体 Debug.Log(\\\"** 物体名字:\\\" + this.gameObject.name); this.gameObject.name直接调用类名 string name = obj.name; string 是c#中的字符串类型 Debug.Log(\\\"**

    2023年04月11日
    浏览(49)
  • 掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南

    变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 \\\'a\\\' 或 \\\'B\\\'。 Char 值用单引号括起来 string - 存储文本,如

    2024年01月17日
    浏览(50)
  • C#声明Employee类实现其构造函数(C#课后练习题-构造函数篇)

    本篇文章的题目为C#的基础练习题,构造函数部分。做这些习题之前,你需要确保已经学习了构造函数的知识。 本篇文章可以用来在学完构造函数后加深印象,也可以用于大学课后习题。 假设你正在开发一个简单的员工管理系统,其中有一个 Employee 类表示员工。请编写一个

    2024年02月07日
    浏览(49)
  • 【05】STM32·HAL库开发-C语言基础知识 | stdint.h介绍 | 位操作 | 宏定义的使用 | 条件编译 | extern声明 | typdef使用 | 结构体、指针、代码规范介绍。

      stdint.h 是从 C99 中引进的一个标准 C 库的文件,可以在MDK5的安装路径:D:MDK5.34ARMARMCCinclude中找到。   stdint.h 定义了很多类型别名,将有符号的char类型定义别名为int8_t等,使用此套别名有易于移植。   在MDK中需要配置才能支持使用S99标准, 默认是勾选的 。   只

    2024年02月08日
    浏览(44)
  • Unity3D C#获取Texture2D像素数据IntPtr指针

    Unity3D调用C++库执行图像处理时,需要快速传递Texture2D纹理像素数据块,获取数据块C++指针(C#中用IntPtr表示) 代码如下 案例

    2024年02月15日
    浏览(58)
  • 学习如何在C#中轻松实现串口数据接收:清晰步骤与实例代码

      概述: 以上C#示例演示了如何使用SerialPort类实现串口数据接收。通过设置串口属性、定义数据接收事件处理程序,你可以轻松地打开串口、监听数据,并在事件处理程序中对接收到的数据进行处理。这提供了一个基本框架,可根据实际需求进行定制。 在C#中实现串口数据接

    2024年02月22日
    浏览(57)
  • 【Unity】C# 创建/读取/解析JSON数据

    判断是否存在JSON数据文件没有则创建并保存

    2024年02月16日
    浏览(71)
  • Unity快速入门教程-创建并启用c#脚本

    提示:本篇文章主要提供新手入门学习,初次发文,多多指教 unity通过c#脚本构建项目逻辑关系,本篇介绍c#脚本创建,启用及其简单示例 提示:以下是本篇文章正文内容,下面案例可供参考 在Project窗口依次单击右键–Create–C#Script,创建脚本,假设命名为【Test】。

    2024年02月11日
    浏览(49)
  • Unity和C#游戏编程入门:创建迷宫小球游戏示例

    💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】 🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】 💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 当涉及到Unity和C#游戏编程入门时,以下是一些示例代码,可以帮助初学者更好地理

    2024年02月08日
    浏览(41)
  • C# 代码创建DLL

    本地代码文件如下: 生成的DLL   反编译DLL结果:   注意:.net 5及以上已不支持,请使用.net 5以下的版本

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包