首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# unsafe 與指標操作完整指南:stackalloc、fixed、非託管記憶體

     編輯:關於C語言

前言

C# 是型別安全的語言,但透過 unsafe 關鍵字可啟用指標操作,適用於高效能運算、互操作非託管代碼、影像處理等場景。本文系統整理指標語法、記憶體管理與實戰技巧。

啟用 unsafe 的專案設定

  1. 專案屬性 → Build → 勾選 "Allow unsafe code"
  2. 或在 .csproj 中加入:<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

核心語法與關鍵字

1. unsafe 範圍宣告


// 類別層級
public unsafe class PointerDemo { }

// 方法層級
public unsafe void ProcessData(byte* data, int length) { }

// 區塊層級
public void SafeMethod()
{
    unsafe 
    {
        int* ptr = stackalloc int[10];
        // ...
    }
}

2. 指標型別支援

分類支援型別說明
實值型別sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, bool所有基本型別
列舉enum 基底型別為整數者可轉換為對應整數指標
結構體不包含參考型別欄位的 struct需為 unmanaged type
void*通用指標不可解參考,僅用於傳遞/轉換

不支援: 類別、陣列、委派、字串、介面、含參考型別欄位的結構體。

3. stackalloc:堆疊記憶體配置


public unsafe void StackAllocDemo()
{
    // 配置 100 個 byte 於堆疊,自動釋放(方法返回時)
    byte* buffer = stackalloc byte[100];
    
    for (int i = 0; i < 100; i++)
        buffer[i] = (byte)i;
    
    // 可直接傳遞給非託管 API
    ProcessBuffer(buffer, 100);
}

// 優點:零 GC 壓力、極快配置/釋放、適合小型暫存緩衝區
// 缺點:堆疊空間有限(預設 1MB),過大導致 StackOverflowException

4. fixed:固定變數位址,防止 GC 移動


public unsafe void FixedDemo()
{
    byte[] managedArray = new byte[1000];
    
    // 固定陣列位址,取得指標
    fixed (byte* ptr = managedArray)
    {
        ptr[0] = 255;
        ptr[999] = 128;
        // 區塊結束自動解除固定
    }
    
    // 字串固定(唯讀)
    string text = "Hello";
    fixed (char* p = text)
    {
        Console.WriteLine(p[0]); // 'H'
    }
}

5. 非託管堆疊記憶體(HeapAlloc/HeapFree)


public unsafe class UnmanagedMemory
{
    private static readonly IntPtr _processHeap = GetProcessHeap();
    private const uint HEAP_ZERO_MEMORY = 0x00000008;

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetProcessHeap();

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, IntPtr dwBytes);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr HeapReAlloc(IntPtr hHeap, uint dwFlags, IntPtr lpMem, IntPtr dwBytes);

    public static void* Alloc(int size)
    {
        void* ptr = HeapAlloc(_processHeap, HEAP_ZERO_MEMORY, (IntPtr)size);
        if (ptr == null) throw new OutOfMemoryException();
        return ptr;
    }

    public static void Free(void* block)
    {
        if (!HeapFree(_processHeap, 0, (IntPtr)block))
            throw new InvalidOperationException("HeapFree failed");
    }

    public static void* ReAlloc(void* block, int size)
    {
        void* ptr = HeapReAlloc(_processHeap, HEAP_ZERO_MEMORY, (IntPtr)block, (IntPtr)size);
        if (ptr == null) throw new OutOfMemoryException();
        return ptr;
    }
}

實戰範例:高效能位元組陣列複製


public unsafe class FastCopy
{
    // 使用指標逐 byte 複製,避免邊界檢查
    public static void Copy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
    {
        if (src == null || dst == null) throw new ArgumentNullException();
        if (count < 0 || srcOffset + count > src.Length || dstOffset + count > dst.Length)
            throw new ArgumentOutOfRangeException();

        fixed (byte* pSrc = src, pDst = dst)
        {
            byte* s = pSrc + srcOffset;
            byte* d = pDst + dstOffset;

            // 向量化複製(簡易版)
            if (s < d)
            {
                // 重疊時從後向前複製
                s += count; d += count;
                while (count-- > 0) *--d = *--s;
            }
            else
            {
                while (count-- > 0) *d++ = *s++;
            }
        }
    }
}

現代替代方案:Span<T> / Memory<T>

.NET Core 2.1+ / .NET Standard 2.1+ 提供 Span<T> 與 Memory<T>,無需 unsafe 即可獲得類似效能:


// 安全、高效、可用於 managed/非託管記憶體
public void ModernCopy(Span src, Span dst)
{
    src.CopyTo(dst); // 內部已優化向量化
}

// 配置非託管記憶體
var buffer = new NativeMemory(1024); // .NET 7+
// 或
var handle = Marshal.AllocHGlobal(1024);
var span = new Span(handle.ToPointer(), 1024);
Marshal.FreeHGlobal(handle);

安全性與除錯建議

  • 最小化 unsafe 範圍:僅在必要區塊標記 unsafe
  • 輸入驗證:指標運算無邊界檢查,務必手動驗證長度
  • 使用 SafeHandle:封裝非託管資源,確保釋放
  • 除錯技巧:Visual Studio 觀察視窗可直接查看指標值與記憶體內容
  • 程式碼分析:啟用 CA2014、CA1401 等規則檢測 unsafe 使用

總結

指標賦予 C# 底層控制力,但在現代 .NET 中,Span<T>、Memory<T>、NativeMemory、Unsafe 類別已提供更安全的高效能替代方案。僅在互操作、極致效能調校、演算法移植時才考慮直接使用 unsafe 指標。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved