C# 中的 sizeof 與非托管類型約束全解析
它的作用是在編譯時獲取非托管類型所占用的字節(jié)數(shù)。它就像是內(nèi)存的“空間丈量尺”,告訴你這個類型在內(nèi)存里到底占了多大的地兒。
在 C# 中,sizeof 是一個用于獲取非托管類型(Unmanaged Type)在內(nèi)存中所占字節(jié)數(shù)(Byte)的運算符。它的本質(zhì)是編譯器指令,在編譯階段就能確定數(shù)值。
1. 它到底在查什么?
sizeof 的核心在于內(nèi)存布局(Memory Layout)。計算機存儲數(shù)據(jù)時,不同的數(shù)據(jù)類型需要占用不同大小的連續(xù)空間。sizeof 告訴程序:如果要為一個變量分配空間,到底需要切出多大的一塊內(nèi)存。
2. 核心語法與約束
sizeof 的用法非常簡單,但受限于 C# 的安全機制:
- 內(nèi)置基礎(chǔ)類型:可以直接使用,如
sizeof(int)。 - 自定義結(jié)構(gòu)體:必須在
unsafe(不安全)上下文中使用。 - 局限性:它不能用于引用類型(Reference Type),如
class,因為引用類型的大小在堆棧上只是一個指針大小,其實際內(nèi)容在堆中分配,且受垃圾回收(GC)影響,布局不固定。
流程圖:sizeof 的執(zhí)行邏輯

3. 代碼示例
基礎(chǔ)用法
對于內(nèi)置類型,sizeof 返回的是可預(yù)見的結(jié)果:
Console.WriteLine(sizeof(byte)); // 輸出: 1 Console.WriteLine(sizeof(int)); // 輸出: 4 Console.WriteLine(sizeof(long)); // 輸出: 8 Console.WriteLine(sizeof(double)); // 輸出: 8
進(jìn)階用法:自定義結(jié)構(gòu)體
當(dāng)處理自定義結(jié)構(gòu)時,需要考慮內(nèi)存對齊(Memory Alignment)。
using System;
struct MyStruct
{
public byte A; // 1 byte
public int B; // 4 bytes
}
class Program
{
static void Main()
{
// 必須開啟 unsafe 編譯選項
unsafe
{
// 結(jié)果是 8,而不是 5。
// 因為編譯器為了 CPU 讀取效率,會對字段進(jìn)行填充(Padding)。
Console.WriteLine($"結(jié)構(gòu)體大小: {sizeof(MyStruct)}");
}
}
}4. 常用使用場景
- 非托管互操作(Interop):當(dāng)你調(diào)用 C/C++ 寫的動態(tài)鏈接庫(DLL)時,需要精確告訴系統(tǒng)你要傳遞多大的數(shù)據(jù)塊。
- 序列化與二進(jìn)制讀寫:在高性能場景下,直接操作
byte[]數(shù)組時,通過sizeof確定偏移量。 - 內(nèi)存池分配:預(yù)先申請一塊內(nèi)存空間,計算總?cè)萘繒r使用。
- 性能優(yōu)化:通過
sizeof觀察結(jié)構(gòu)體對齊情況,減少內(nèi)存碎片的浪費。
除了基礎(chǔ)的內(nèi)存分配,在資深軟件工程師眼中,sizeof 更多用于底層優(yōu)化和精密控制。
A. 棧上內(nèi)存分配 (stackalloc)
在追求極致性能的場景(如解析高頻金融數(shù)據(jù)),我們避開堆內(nèi)存,直接在棧上開辟空間。
// 申請 100 個 int 大小的棧空間 Span<int> numbers = stackalloc int[100]; // 在底層邏輯中,它等同于申請了 100 * sizeof(int) 字節(jié)
B. 泛型約束中的內(nèi)存計算
在 C# 7.3 之后,我們可以使用 where T : unmanaged 約束。配合 sizeof,可以編寫極其通用的高性能代碼。
public unsafe void CopyData<T>(T* source, T* destination, int count) where T : unmanaged
{
// 自動根據(jù) T 的類型計算需要復(fù)制的字節(jié)總數(shù)
long totalBytes = (long)count * sizeof(T);
Buffer.MemoryCopy(source, destination, totalBytes, totalBytes);
}
C. 固定內(nèi)存塊偏移 (Fixed Memory Offset)
當(dāng)你在處理復(fù)雜的二進(jìn)制協(xié)議(如網(wǎng)絡(luò)封包、視頻解碼)時,sizeof 是計算偏移量的唯一準(zhǔn)繩。
| sizeof(sbyte) | 1 |
|---|---|
| sizeof(byte) | 1 |
| sizeof(short) | 2 |
| sizeof(ushort) | 2 |
| sizeof(int) | 4 |
| sizeof(uint) | 4 |
| sizeof(long) | 8 |
| sizeof(ulong) | 8 |
| sizeof(char) | 2 |
| sizeof(float) | 4 |
| sizeof(double) | 8 |
| sizeof(decimal) | 16 |
| sizeof(bool) | 1 |
5. Marshal.SizeOf
在實際開發(fā)中,Marshal.SizeOf 最經(jīng)典的舞臺就是 Win32 API 調(diào)用。
很多 Windows 底層的函數(shù)要求你傳入一個結(jié)構(gòu)體,并且在這個結(jié)構(gòu)體的第一個字段里,你必須明確告訴系統(tǒng):“這個結(jié)構(gòu)體本人占用了多少字節(jié)”。如果這個數(shù)字填錯了,系統(tǒng)為了安全會直接拒絕執(zhí)行。
1. 為什么要運行時測量?
Windows API 設(shè)計時為了向前兼容,經(jīng)常會在同一個結(jié)構(gòu)體的后續(xù)版本中增加字段。系統(tǒng)通過檢查你傳入的 Size(大?。﹣砼袛嗄闶褂玫氖悄囊粋€版本的結(jié)構(gòu)體,從而決定如何處理內(nèi)存。
2. 核心場景:獲取系統(tǒng)托盤圖標(biāo)信息
假設(shè)我們要獲取 Windows 任務(wù)欄通知區(qū)域(托盤)的圖標(biāo)信息,會用到 NOTIFYICONDATA 結(jié)構(gòu)體。
using System;
using System.Runtime.InteropServices;
// 定義符合 Win32 布局的結(jié)構(gòu)體
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NOTIFYICONDATA
{
public int cbSize; // 結(jié)構(gòu)體本身的大?。P(guān)鍵?。?
public IntPtr hWnd; // 窗口句柄
public uint uID; // 圖標(biāo) ID
public uint uFlags; // 標(biāo)志位
public uint uCallbackMessage;
public IntPtr hIcon; // 圖標(biāo)句柄
// ... 其他字段省略
}
class Program
{
static void Main()
{
NOTIFYICONDATA data = new NOTIFYICONDATA();
// 場景:初始化結(jié)構(gòu)體時,必須告知系統(tǒng)該結(jié)構(gòu)體的大小
// 這里不能用 sizeof(NOTIFYICONDATA),因為在沒有 unsafe 的環(huán)境下無法編譯
// 且 Marshal.SizeOf 會考慮 CharSet 帶來的字符編碼寬度差異
data.cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
Console.WriteLine($"結(jié)構(gòu)體在托管內(nèi)存中的大小推算為: {data.cbSize} 字節(jié)");
// 模擬調(diào)用 Win32 API
// Shell_NotifyIcon(NIM_ADD, ref data);
}
}3. 為什么這個例子不用 sizeof?
- 無需 unsafe:
Marshal.SizeOf可以在普通的托管代碼中運行,不需要開啟項目的“允許不安全代碼”開關(guān)。 - 封送感知 (Marshaling Awareness):
Marshal.SizeOf會考慮StructLayout屬性。例如,如果結(jié)構(gòu)體里有string,sizeof根本無法處理,而Marshal.SizeOf會根據(jù)你指定的CharSet(如 Unicode 占 2 字節(jié),Ansi 占 1 字節(jié))來計算它轉(zhuǎn)化成 C 語言格式后的大小。
4. 流程圖:Marshal.SizeOf 如何在運行時工作

5. sizeof vs Marshal.SizeOf
它位于 System.Runtime.InteropServices 命名空間下。只要你安裝了 .NET SDK,就可以直接通過 using 引用它。
- 本質(zhì)區(qū)別:
- sizeof:**編譯時(Compile-time)**指令。它直接硬編碼數(shù)值到程序中,速度極快,但只能用于非托管類型。
- Marshal.SizeOf:運行時(Runtime)方法。它通過反射(Reflection)去測量對象在內(nèi)存布局(特別是轉(zhuǎn)換為非托管格式后)的大小,支持
struct實例,也能處理一些被裝箱的對象,但性能開銷比sizeof大。
| 特性 | sizeof | Marshal.SizeOf |
|---|---|---|
| 性能 | 極高(等同于直接寫數(shù)字) | 一般(有運行時開銷) |
| 上下文要求 | 涉及自定義結(jié)構(gòu)需 unsafe | 無需 unsafe |
| 靈活性 | 只能用類型名,不能用變量名 | 可以直接傳入變量實例 |
| 主要目的 | 算偏移量、算指針位移 | 調(diào)用 Win32 API、處理復(fù)雜封送 |
6. 易混淆
- 句柄 (Handle/IntPtr):可以理解為系統(tǒng)資源的“代號”或“門票”。在 64 位系統(tǒng)下,它的大小等同于
long(8 字節(jié))。 - 封送 (Marshaling):將 C# 中的對象搬運到內(nèi)存緩沖區(qū),并轉(zhuǎn)換成 C/C++ 能看懂的格式的過程。
- StructLayout (LayoutKind.Sequential):強制要求編譯器按照你在代碼里寫字段的順序來排列內(nèi)存,不要為了優(yōu)化而打亂順序,否則 Windows 系統(tǒng)會讀錯數(shù)據(jù)。
- 非托管類型 (Unmanaged Type):指不需要垃圾回收器(GC)管理的類型。包括 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, 枚舉, 以及只包含這些類型的結(jié)構(gòu)體。
- 內(nèi)存對齊 (Memory Alignment):CPU 讀取內(nèi)存時不是一個字節(jié)一個字節(jié)讀的,而是以“塊”(如 4 或 8 字節(jié))為單位。為了提高讀取速度,編譯器會在數(shù)據(jù)結(jié)構(gòu)之間插入一些無用的空字節(jié),使數(shù)據(jù)起始地址落在塊的邊界上。
- 填充 (Padding):內(nèi)存對齊過程中自動添加的空閑字節(jié)。
- unsafe 上下文:C# 默認(rèn)是內(nèi)存安全的,不允許直接操作地址。通過
unsafe關(guān)鍵字,你可以在特定區(qū)域內(nèi)像 C 語言一樣直接操作指針和查看原始內(nèi)存布局。
到此這篇關(guān)于C# 中的 sizeof 與非托管類型約束全解析的文章就介紹到這了,更多相關(guān)C# sizeof 與非托管類型約束內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用Linq to XML進(jìn)行XPath查詢的代碼實現(xiàn)
最近在用到HtmlAgliltyPack進(jìn)行結(jié)點查詢時,發(fā)現(xiàn)這里選擇結(jié)點使用的是XPath,所以這里總結(jié)一下在C#中使用XPath查詢XML的方法,習(xí)慣了用Linq,這里也是用的Linq to xml的,需要的朋友可以參考下2024-08-08
在C# .NET Core中繪制Chart圖表并導(dǎo)出到PDF的實現(xiàn)方法
正好最近的項目需要在后端繪制Chart圖標(biāo)并進(jìn)行導(dǎo)出,查閱相關(guān)資料后想著記錄以下,本文將詳細(xì)介紹在.NET Core環(huán)境中如何繪制Chart圖表并將其導(dǎo)出到PDF文檔中,并分析這種實現(xiàn)方式的技術(shù)價值和應(yīng)用場景,需要的朋友可以參考下2025-10-10
C#簡單實現(xiàn)在網(wǎng)頁上發(fā)郵件的案例
本文分享一個C#利用SMTP發(fā)送郵件的案例,提供了前后臺代碼,方便大家學(xué)習(xí)。2016-03-03

