C#怎么使用Unsafe不安全代码

来源:语言推理作者:北京GEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《C#怎么使用Unsafe不安全代码》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#怎么使用Unsafe不安全代码》有用,将其分享出去将是对创作者最好的鼓励。

C#作为一门托管语言,默认情况下会由CLR负责内存管理和类型安全检查,但在一些高性能场景、底层交互场景或者需要直接操作内存的场景中,开发者可以使用Unsafe不安全代码来绕过部分托管限制,获得更灵活的内存操作能力。

C#怎么使用Unsafe不安全代码

启用Unsafe代码环境

默认情况下C#项目是不允许编写不安全代码的,需要先开启项目的不安全代码支持。如果是使用Visual Studio创建的项目,可以在项目属性中勾选允许不安全代码选项。如果是手动编辑项目文件,需要在csproj文件中添加对应的配置:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <!-- 开启不安全代码支持 -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

Unsafe代码基础:指针操作

不安全代码的核心能力之一是支持指针操作,指针可以直接指向内存地址,通过指针可以直接读写对应内存位置的数据。使用指针需要先声明指针类型,指针类型的声明方式是在类型后面添加星号。

指针的基本声明与使用

下面的示例展示了如何声明指针、获取变量的内存地址以及通过指针修改变量的值:

using System;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int num = 10;
            // 声明int类型的指针,使用fixed固定变量地址,避免GC移动变量
            fixed (int* p = &num)
            {
                Console.WriteLine($"变量num的原始值:{num}");
                Console.WriteLine($"指针p指向的地址:{(long)p}");
                Console.WriteLine($"指针p指向的值:{*p}");
                // 通过指针修改值
                *p = 20;
                Console.WriteLine($"通过指针修改后num的值:{num}");
            }
        }
    }
}

注意这里使用了fixed语句,因为托管堆中的对象可能会被GC(垃圾回收器)移动,直接使用指针指向托管变量可能会出现地址失效的问题,fixed可以临时固定变量的内存地址,在fixed块执行期间变量不会被GC移动。

指针的运算

指针支持基本的加减运算,运算的步长由指针指向的类型的大小决定,比如int*类型的指针加1,地址会增加4个字节(因为int类型占4字节)。

using System;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = { 1, 2, 3, 4, 5 };
            fixed (int* p = arr)
            {
                // 遍历数组的每个元素
                for (int i = 0; i < arr.Length; i++)
                {
                    // 指针加i,指向第i个元素
                    Console.WriteLine($"arr[{i}]的值:{*(p + i)}");
                }
            }
        }
    }
}

Unsafe辅助类的常用功能

除了直接使用指针,.NET还提供了System.Runtime.CompilerServices.Unsafe辅助类,这个类提供了一系列静态方法,简化不安全代码的操作,不需要手动编写很多指针转换逻辑。

SizeOf方法

Unsafe.SizeOf<T>()可以获取指定类型在内存中占用的字节数,和sizeof运算符类似,但支持更多类型。

using System;
using System.Runtime.CompilerServices;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"int类型占用字节数:{Unsafe.SizeOf<int>()}");
            Console.WriteLine($"long类型占用字节数:{Unsafe.SizeOf<long>()}");
            Console.WriteLine($"自定义结构体占用字节数:{Unsafe.SizeOf<TestStruct>()}");
        }
    }

    struct TestStruct
    {
        public int A;
        public long B;
    }
}

As方法:类型转换

Unsafe.As<TFrom, TTo>(ref TFrom source)可以在不进行类型检查的情况下,把一种类型的引用转换为另一种类型的引用,通常用于需要 reinterpret cast 的场景。

using System;
using System.Runtime.CompilerServices;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int num = 0x12345678;
            // 把int的引用转换为byte数组的引用
            ref byte byteRef = ref Unsafe.As<int, byte>(ref num);
            // 输出每个字节的值,注意大小端问题
            Console.WriteLine($"int的字节表示:{byteRef:X2} {Unsafe.Add(ref byteRef, 1):X2} {Unsafe.Add(ref byteRef, 2):X2} {Unsafe.Add(ref byteRef, 3):X2}");
        }
    }
}

Add方法:指针偏移

Unsafe.Add<T>(ref T source, int index)可以在不声明指针的情况下,对引用进行偏移操作,获取偏移后位置的引用。

using System;
using System.Runtime.CompilerServices;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = { 10, 20, 30, 40 };
            ref int firstElement = ref arr[0];
            // 偏移1个位置,获取第二个元素的引用
            ref int secondElement = ref Unsafe.Add(ref firstElement, 1);
            Console.WriteLine($"第二个元素的值:{secondElement}");
            // 修改偏移后的引用,原数组也会被修改
            secondElement = 200;
            Console.WriteLine($"修改后数组第二个元素的值:{arr[1]}");
        }
    }
}

内存复制:Copy方法

Unsafe.Copy系列方法可以快速进行内存块的复制,比普通的数组复制效率更高,适合大内存块的复制场景。

using System;
using System.Runtime.CompilerServices;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] source = { 1, 2, 3, 4, 5 };
            int[] target = new int[5];
            // 把source数组的内容复制到target数组
            Unsafe.CopyBlock(ref target[0], ref source[0], (uint)(source.Length * sizeof(int)));
            Console.WriteLine($"复制后的target数组:{string.Join(",", target)}");
        }
    }
}

不安全代码的内存分配

除了操作托管内存,不安全代码还可以在非托管堆上分配内存,这种内存不会由GC管理,需要手动释放,否则会造成内存泄漏。

使用stackalloc分配栈内存

stackalloc可以在栈上分配一块内存,这块内存会在方法执行结束后自动释放,不需要手动管理,但是大小不能太大,否则会导致栈溢出。

using System;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 在栈上分配10个int大小的内存
            int* p = stackalloc int[10];
            for (int i = 0; i < 10; i++)
            {
                p[i] = i * 10;
            }
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"p[{i}]的值:{p[i]}");
            }
        }
    }
}

使用Marshal分配非托管堆内存

如果需要分配较大的非托管内存,可以使用System.Runtime.InteropServices.Marshal类的方法,分配的内存需要手动调用FreeHGlobal释放。

using System;
using System.Runtime.InteropServices;

namespace UnsafeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int size = 10 * sizeof(int);
            // 分配非托管内存
            IntPtr ptr = Marshal.AllocHGlobal(size);
            try
            {
                // 把指针转换为int指针
                int* p = (int*)ptr;
                for (int i = 0; i < 10; i++)
                {
                    p[i] = i + 1;
                }
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine($"非托管内存第{i}个值:{p[i]}");
                }
            }
            finally
            {
                // 释放非托管内存
                Marshal.FreeHGlobal(ptr);
            }
        }
    }
}

使用Unsafe代码的注意事项

  • 不安全代码会绕过CLR的类型安全检查,错误的指针操作可能会导致内存访问越界、程序崩溃甚至安全漏洞,使用前需要充分理解指针和内存的原理。
  • 使用fixed固定托管对象时,固定的时间尽量不要太长,否则会影响GC的回收效率,可能导致内存碎片增加。
  • 非托管内存需要手动释放,一定要确保在异常情况下也能正确释放内存,避免内存泄漏。
  • 不安全代码的方法需要标记unsafe关键字,包含不安全代码的类或者方法都需要在对应的声明处添加该关键字。

总的来说,Unsafe不安全代码是C#提供的高级特性,适合在对性能要求极高或者需要和底层交互的场景使用,普通业务场景如果没有特殊需求,优先使用安全的托管代码,减少出错风险。

C#Unsafe内存操作指针操作修改时间:2026-06-30 10:09:50

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。