C# 不安全代码
`unsafe`、指针、`fixed`、`stackalloc`、以及何时值得用。
开启
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>unsafe static void Main()
{
int x = 42;
int* p = &x;
Console.WriteLine((long)p);
Console.WriteLine(*p);
*p = 100;
Console.WriteLine(x);
}140734732845112
42
100指针操作
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));
}10 20 30 40
10,99,30,40fixed 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
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]);0,1,4,9
1Span<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.
实际用途
// 图像处理:直接操作像素缓冲区
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));255,127,0该不该用
| Situation | Verdict |
|---|---|
| 普通业务代码 | 不用 |
| 处理大数组/像素/音频 | 先试 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 例子
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>()); // 不需要 unsafe4
4sizeof needs unsafe for unmanaged types; Marshal.SizeOf<T>() is the safe runtime alternative — slightly slower but no unsafe needed. Prefer it.
开启
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>unsafe static void Main()
{
int x = 42;
int* p = &x;
Console.WriteLine((long)p);
Console.WriteLine(*p);
*p = 100;
Console.WriteLine(x);
}140734732845112
42
100指针操作
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));
}10 20 30 40
10,99,30,40fixed 告诉 GC「这块内存别动」,因为 GC 压缩堆时会移动对象,指针就失效了。fixed 块结束后地址可能变,所以指针不能存起来跨块使用。
stackalloc
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]);0,1,4,9
1Span<T> / Memory<T> 是 .NET Core 2.1 引入的「安全指针」:有边界检查、无 GC 分配、能指向栈或堆。**绝大多数需要指针的场景,用 Span<T> 就够了**,不必开 unsafe。
实际用途
// 图像处理:直接操作像素缓冲区
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));255,127,0该不该用
| 场景 | 结论 |
|---|---|
| 普通业务代码 | 不用 |
| 处理大数组/像素/音频 | 先试 Span<T> |
| 与 C 库互操作(P/Invoke) | 必须用指针 |
| 需要精确内存布局 | 用 [StructLayout] + 指针 |
| 极致性能且已验证瓶颈 | 可以用,但要压测 |
unsafe 代码不受 GC 和边界检查保护:越界写会静默破坏其他对象的内存,症状可能在几百行之外才爆发。用之前必须有基准测试证明它是瓶颈,且要有充分的单元测试。P/Invoke 例子
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>()); // 不需要 unsafe4
4sizeof 对非托管类型需要 unsafe;Marshal.SizeOf<T>() 是安全版本,运行期计算。后者稍慢但不用开 unsafe,优先用它。