C# 不安全代码

`unsafe`、指针、`fixed`、`stackalloc`、以及何时值得用。

Advanced
Key Points`unsafe` 块里可以用指针,绕过 GC 和边界检查——换来性能,也换来崩溃风险。

开启

XML
<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
C#
unsafe static void Main()
{
    int x = 42;
    int* p = &x;

    Console.WriteLine((long)p);
    Console.WriteLine(*p);

    *p = 100;
    Console.WriteLine(x);
}
Output
140734732845112
42
100

指针操作

C#
unsafe
{
    int[] arr = { 10, 20, 30, 40 };

    // 数组默认可能被 GC 移动,fixed 固定住地址
    fixed (int* p = arr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            Console.Write(*(p + i) + " ");
        }
        Console.WriteLine();

        *(p + 1) = 99;          // 直接改内存
    }

    Console.WriteLine(string.Join(",", arr));
}
Output
10 20 30 40 
10,99,30,40

fixed tells the GC not to move this memory — compaction would invalidate the pointer. The address may change after the block, so never store a pointer beyond it.

stackalloc

C#
unsafe
{
    // 在栈上分配,不用 GC,作用域结束自动回收
    Span<int> buf = stackalloc int[4];
    for (int i = 0; i < buf.Length; i++) buf[i] = i * i;

    Console.WriteLine(string.Join(",", buf.ToArray()));
}

// Span 是安全的封装,不需要 unsafe
Span<byte> safe = stackalloc byte[3];
safe[0] = 1;
Console.WriteLine(safe[0]);
Output
0,1,4,9
1

Span<T>/Memory<T> are .NET Core 2.1's "safe pointers": bounds-checked, allocation-free, able to point at stack or heap. **Most cases that used to need pointers are served by Span<T>** without unsafe.

实际用途

C#
// 图像处理:直接操作像素缓冲区
unsafe static void Invert(byte[] pixels)
{
    fixed (byte* p = pixels)
    {
        for (int i = 0; i < pixels.Length; i++)
        {
            p[i] = (byte)(255 - p[i]);
        }
    }
}

var px = new byte[] { 0, 128, 255 };
Invert(px);
Console.WriteLine(string.Join(",", px));
Output
255,127,0

该不该用

SituationVerdict
普通业务代码不用
处理大数组/像素/音频先试 Span<T>
与 C 库互操作(P/Invoke)必须用指针
需要精确内存布局用 [StructLayout] + 指针
极致性能且已验证瓶颈可以用,但要压测
unsafe code is outside GC and bounds protection: an out-of-range write silently corrupts other objects and surfaces hundreds of lines away. Only use it after benchmarks prove the bottleneck, with solid tests.

P/Invoke 例子

C#
using System.Runtime.InteropServices;

// 调用 Windows API
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);

unsafe
{
    int size = sizeof(int);          // 需要 unsafe
    Console.WriteLine(size);
}

Console.WriteLine(Marshal.SizeOf<int>());   // 不需要 unsafe
Output
4
4

sizeof needs unsafe for unmanaged types; Marshal.SizeOf<T>() is the safe runtime alternative — slightly slower but no unsafe needed. Prefer it.

Friends & Partners