Unity C# 中的 Unsafe 代码详解

1. 什么是 Unsafe 代码?

Unsafe 代码是 C# 中允许直接操作内存和指针的特性,它绕过了 CLR(公共语言运行时)的一些安全检查。在 Unity 中,使用 Unsafe 代码可以:

  • 直接操作内存地址

  • 使用指针类型

  • 进行底层内存操作

  • 提高特定场景下的性能

2. 启用 Unsafe 代码

在 Unity 项目中,需要在以下位置启用:

  1. Player Settings

    • Edit → Project Settings → Player

    • Other Settings → Configuration

    • 勾选 "Allow 'unsafe' Code"

  2. Assembly Definition(如果需要):

json

{
  "allowUnsafeCode": true
}

3. Unsafe 上下文

基本语法

csharp

unsafe class UnsafeClass
{
    // 类中的不安全代码
}

unsafe void UnsafeMethod()
{
    // 方法中的不安全代码
}

void SafeMethod()
{
    unsafe
    {
        // 代码块中的不安全代码
    }
}

4. 指针基础

声明和使用指针

csharp

unsafe void PointerBasics()
{
    int value = 42;
    int* pointer = &value;  // & 获取地址
    
    Debug.Log($"值: {value}");
    Debug.Log($"地址: {(long)pointer:X}");
    Debug.Log($"通过指针访问: {*pointer}");  // * 解引用
    
    *pointer = 100;  // 通过指针修改值
    Debug.Log($"修改后的值: {value}");
}

指针类型

csharp

unsafe void PointerTypes()
{
    // 基本类型指针
    int* intPtr;
    float* floatPtr;
    byte* bytePtr;
    
    // void 指针(无类型指针)
    void* voidPtr;
    
    // 结构体指针
    Vector3* vectorPtr;
}

5. fixed 关键字

固定托管变量

csharp

unsafe void FixedExample()
{
    int[] array = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    // 使用 fixed 固定数组在内存中的位置
    fixed (int* ptr = array)
    {
        for (int i = 0; i < 10; i++)
        {
            Debug.Log($"array[{i}] = {*(ptr + i)}");
            
            // 修改数组元素
            *(ptr + i) = i * 10;
        }
    }
    
    // 固定多个变量
    fixed (int* p1 = &array[0], p2 = &array[5])
    {
        // 同时操作多个指针
    }
}

固定字符串

csharp

unsafe void FixedString()
{
    string text = "Hello";
    
    fixed (char* p = text)
    {
        for (int i = 0; i < text.Length; i++)
        {
            Debug.Log($"字符 {i}: {*(p + i)}");
        }
    }
}

6. 指针运算

csharp

unsafe void PointerArithmetic()
{
    int[] numbers = new int[5] { 10, 20, 30, 40, 50 };
    
    fixed (int* ptr = numbers)
    {
        int* current = ptr;
        
        // 指针加法
        current += 2;  // 移动到第三个元素
        Debug.Log($"当前位置的值: {*current}");  // 30
        
        // 指针减法
        current -= 1;  // 回到第二个元素
        Debug.Log($"新位置的值: {*current}");  // 20
        
        // 指针比较
        int* start = ptr;
        int* end = ptr + 4;
        Debug.Log($"start < end: {start < end}");  // true
        
        // 指针差
        long difference = end - start;
        Debug.Log($"指针差: {difference}");  // 4
    }
}

7. 结构体指针

csharp

unsafe struct Point3D
{
    public float x;
    public float y;
    public float z;
}

unsafe void StructPointerExample()
{
    Point3D point = new Point3D { x = 1.0f, y = 2.0f, z = 3.0f };
    Point3D* ptr = &point;
    
    // 访问结构体成员
    Debug.Log($"x: {ptr->x}");  // 使用 -> 操作符
    Debug.Log($"y: {(*ptr).y}"); // 使用 (*ptr). 语法
    
    // 修改结构体成员
    ptr->x = 10.0f;
    Debug.Log($"修改后的 x: {point.x}");
}

8. 栈内存分配

csharp

unsafe void StackAllocExample()
{
    // 在栈上分配内存(避免GC)
    int* numbers = stackalloc int[5];
    
    for (int i = 0; i < 5; i++)
    {
        numbers[i] = i * i;
    }
    
    // 读取数据
    for (int i = 0; i < 5; i++)
    {
        Debug.Log($"numbers[{i}] = {numbers[i]}");
    }
    
    // 栈分配结构体数组
    Vector3* vectors = stackalloc Vector3[3];
    for (int i = 0; i < 3; i++)
    {
        vectors[i] = new Vector3(i, i * 2, i * 3);
    }
}

9. Unity 中的实际应用

高性能数学运算

csharp

unsafe void ProcessVertices(Vector3[] vertices, Matrix4x4 matrix)
{
    fixed (Vector3* verticesPtr = vertices)
    {
        Vector3* current = verticesPtr;
        int length = vertices.Length;
        
        for (int i = 0; i < length; i++)
        {
            // 使用指针进行矩阵变换(避免临时变量)
            current->x = matrix.m00 * current->x + matrix.m01 * current->y + matrix.m02 * current->z + matrix.m03;
            current->y = matrix.m10 * current->x + matrix.m11 * current->y + matrix.m12 * current->z + matrix.m13;
            current->z = matrix.m20 * current->x + matrix.m21 * current->y + matrix.m22 * current->z + matrix.m23;
            
            current++;
        }
    }
}

图像处理

csharp

unsafe void ProcessTexture(Texture2D texture)
{
    var colors = texture.GetPixels32();
    
    fixed (Color32* colorsPtr = colors)
    {
        Color32* current = colorsPtr;
        int pixelCount = colors.Length;
        
        for (int i = 0; i < pixelCount; i++)
        {
            // 灰度化处理
            byte gray = (byte)((current->r * 0.299f + current->g * 0.587f + current->b * 0.114f));
            current->r = gray;
            current->g = gray;
            current->b = gray;
            
            current++;
        }
    }
    
    texture.SetPixels32(colors);
    texture.Apply();
}

10. 与 NativeArray 和 Jobs System 结合

csharp

using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;

unsafe void ProcessWithJobs()
{
    NativeArray<float> data = new NativeArray<float>(1000, Allocator.TempJob);
    
    // 获取原生指针
    float* dataPtr = (float*)data.GetUnsafePtr();
    
    // 使用指针进行初始化
    for (int i = 0; i < data.Length; i++)
    {
        dataPtr[i] = i * 0.1f;
    }
    
    // 创建并调度Job
    var job = new ParallelProcessingJob
    {
        Data = data,
        DataPtr = dataPtr
    };
    
    JobHandle handle = job.Schedule(data.Length, 64);
    handle.Complete();
    
    data.Dispose();
}

struct ParallelProcessingJob : IJobParallelFor
{
    public NativeArray<float> Data;
    [NativeDisableUnsafePtrRestriction]
    public unsafe float* DataPtr;
    
    public void Execute(int index)
    {
        // 在Job中直接使用指针
        DataPtr[index] = Mathf.Sin(DataPtr[index]);
    }
}

11. 内存复制和操作

csharp

unsafe void MemoryOperations()
{
    // 使用 Buffer.MemoryCopy(高性能内存复制)
    byte[] source = new byte[1024];
    byte[] destination = new byte[1024];
    
    fixed (byte* srcPtr = source, dstPtr = destination)
    {
        // 复制内存
        System.Buffer.MemoryCopy(srcPtr, dstPtr, destination.Length, source.Length);
    }
    
    // 归零内存区域
    int[] array = new int[100];
    fixed (int* ptr = array)
    {
        // 使用指针快速清零
        UnsafeUtility.MemClear(ptr, array.Length * sizeof(int));
    }
}

12. 安全注意事项

常见问题

  1. 内存泄漏:忘记释放非托管内存

  2. 悬垂指针:访问已释放的内存

  3. 缓冲区溢出:访问超出分配范围的内存

  4. 内存对齐:未对齐的内存访问可能导致性能问题或崩溃

最佳实践

csharp

unsafe void SafeUnsafePractices()
{
    // 1. 总是使用 fixed 固定托管变量
    // 2. 检查指针是否为 null
    // 3. 验证数组边界
    // 4. 避免在栈上分配大内存
    // 5. 使用 using 语句管理 NativeArray 等资源
    
    try
    {
        int[] data = new int[100];
        fixed (int* ptr = data)
        {
            // 安全地使用指针
            if (ptr != null)
            {
                // 操作指针
            }
        }
    }
    catch (System.Exception e)
    {
        Debug.LogError($"Unsafe 代码错误: {e.Message}");
    }
}

13. 性能对比

csharp

void PerformanceComparison()
{
    const int SIZE = 1000000;
    float[] array = new float[SIZE];
    
    // 安全版本
    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
    for (int i = 0; i < SIZE; i++)
    {
        array[i] = Mathf.Sqrt(array[i]);
    }
    sw.Stop();
    Debug.Log($"安全版本: {sw.ElapsedMilliseconds}ms");
    
    // Unsafe 版本
    sw.Restart();
    unsafe
    {
        fixed (float* ptr = array)
        {
            for (int i = 0; i < SIZE; i++)
            {
                ptr[i] = Mathf.Sqrt(ptr[i]);
            }
        }
    }
    sw.Stop();
    Debug.Log($"Unsafe 版本: {sw.ElapsedMilliseconds}ms");
}

总结

使用场景

  1. 性能关键代码:需要极致优化的算法

  2. 原生代码交互:与 C/C++ 库交互

  3. 内存操作:需要直接内存操作的任务

  4. 数据处理:大规模数据处理和变换

注意事项

  1. 平台兼容性:某些平台可能限制 Unsafe 代码

  2. 调试难度:指针错误难以调试

  3. 团队协作:确保团队成员理解 Unsafe 代码

  4. 代码审查:严格审查所有 Unsafe 代码

Unsafe 代码是强大的工具,但应该谨慎使用。在 Unity 中,通常只有在性能瓶颈确实存在且其他优化手段无效时,才应考虑使用 Unsafe 代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值