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

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

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

