C# 中的 sizeof 与非托管类型约束全解析

 更新时间:2026年03月30日 09:02:00   作者:MutoKazuo  
在 C# 中,sizeof 是一个用于获取非托管类型(Unmanaged Type)在内存中所占字节数(Byte)的运算符,这篇文章主要介绍了深入理解 C# 中的 sizeof 与非托管类型约束,需要的朋友可以参考下

它的作用是在编译时获取非托管类型所占用的字节数。它就像是内存的“空间丈量尺”,告诉你这个类型在内存里到底占了多大的地儿。

在 C# 中,sizeof 是一个用于获取非托管类型(Unmanaged Type)内存中所占字节数(Byte)的运算符。它的本质是编译器指令,在编译阶段就能确定数值。

1. 它到底在查什么?

sizeof 的核心在于内存布局(Memory Layout)。计算机存储数据时,不同的数据类型需要占用不同大小的连续空间。sizeof 告诉程序:如果要为一个变量分配空间,到底需要切出多大的一块内存。

2. 核心语法与约束

sizeof 的用法非常简单,但受限于 C# 的安全机制:

  • 内置基础类型:可以直接使用,如 sizeof(int)
  • 自定义结构体:必须在 unsafe(不安全)上下文中使用。
  • 局限性:它不能用于引用类型(Reference Type),如 class,因为引用类型的大小在堆栈上只是一个指针大小,其实际内容在堆中分配,且受垃圾回收(GC)影响,布局不固定。

流程图:sizeof 的执行逻辑

3. 代码示例

基础用法

对于内置类型,sizeof 返回的是可预见的结果:

Console.WriteLine(sizeof(byte));   // 输出: 1
Console.WriteLine(sizeof(int));    // 输出: 4
Console.WriteLine(sizeof(long));   // 输出: 8
Console.WriteLine(sizeof(double)); // 输出: 8

进阶用法:自定义结构体

当处理自定义结构时,需要考虑内存对齐(Memory Alignment)

using System;
struct MyStruct
{
    public byte A; // 1 byte
    public int B;  // 4 bytes
}
class Program
{
    static void Main()
    {
        // 必须开启 unsafe 编译选项
        unsafe
        {
            // 结果是 8,而不是 5。
            // 因为编译器为了 CPU 读取效率,会对字段进行填充(Padding)。
            Console.WriteLine($"结构体大小: {sizeof(MyStruct)}");
        }
    }
}

4. 常用使用场景

  • 非托管互操作(Interop):当你调用 C/C++ 写的动态链接库(DLL)时,需要精确告诉系统你要传递多大的数据块。
  • 序列化与二进制读写:在高性能场景下,直接操作 byte[] 数组时,通过 sizeof 确定偏移量。
  • 内存池分配:预先申请一块内存空间,计算总容量时使用。
  • 性能优化:通过 sizeof 观察结构体对齐情况,减少内存碎片的浪费。

除了基础的内存分配,在资深软件工程师眼中,sizeof 更多用于底层优化精密控制

A. 栈上内存分配 (stackalloc)

在追求极致性能的场景(如解析高频金融数据),我们避开堆内存,直接在栈上开辟空间。

// 申请 100 个 int 大小的栈空间
Span<int> numbers = stackalloc int[100];
// 在底层逻辑中,它等同于申请了 100 * sizeof(int) 字节

B. 泛型约束中的内存计算

在 C# 7.3 之后,我们可以使用 where T : unmanaged 约束。配合 sizeof,可以编写极其通用的高性能代码。

public unsafe void CopyData<T>(T* source, T* destination, int count) where T : unmanaged
{
    // 自动根据 T 的类型计算需要复制的字节总数
    long totalBytes = (long)count * sizeof(T);
    Buffer.MemoryCopy(source, destination, totalBytes, totalBytes);
}

C. 固定内存块偏移 (Fixed Memory Offset)

当你在处理复杂的二进制协议(如网络封包、视频解码)时,sizeof 是计算偏移量的唯一准绳。

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

在实际开发中,Marshal.SizeOf 最经典的舞台就是 Win32 API 调用

很多 Windows 底层的函数要求你传入一个结构体,并且在这个结构体的第一个字段里,你必须明确告诉系统:“这个结构体本人占用了多少字节”。如果这个数字填错了,系统为了安全会直接拒绝执行。

1. 为什么要运行时测量?

Windows API 设计时为了向前兼容,经常会在同一个结构体的后续版本中增加字段。系统通过检查你传入的 Size(大小)来判断你使用的是哪一个版本的结构体,从而决定如何处理内存。

2. 核心场景:获取系统托盘图标信息

假设我们要获取 Windows 任务栏通知区域(托盘)的图标信息,会用到 NOTIFYICONDATA 结构体。

using System;
using System.Runtime.InteropServices;
// 定义符合 Win32 布局的结构体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct NOTIFYICONDATA
{
    public int cbSize;        // 结构体本身的大小(关键!)
    public IntPtr hWnd;       // 窗口句柄
    public uint uID;          // 图标 ID
    public uint uFlags;       // 标志位
    public uint uCallbackMessage;
    public IntPtr hIcon;      // 图标句柄
    // ... 其他字段省略
}
class Program
{
    static void Main()
    {
        NOTIFYICONDATA data = new NOTIFYICONDATA();
        // 场景:初始化结构体时,必须告知系统该结构体的大小
        // 这里不能用 sizeof(NOTIFYICONDATA),因为在没有 unsafe 的环境下无法编译
        // 且 Marshal.SizeOf 会考虑 CharSet 带来的字符编码宽度差异
        data.cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
        Console.WriteLine($"结构体在托管内存中的大小推算为: {data.cbSize} 字节");
        // 模拟调用 Win32 API
        // Shell_NotifyIcon(NIM_ADD, ref data);
    }
}

3. 为什么这个例子不用 sizeof?

  1. 无需 unsafeMarshal.SizeOf 可以在普通的托管代码中运行,不需要开启项目的“允许不安全代码”开关。
  2. 封送感知 (Marshaling Awareness)Marshal.SizeOf 会考虑 StructLayout 属性。例如,如果结构体里有 stringsizeof 根本无法处理,而 Marshal.SizeOf 会根据你指定的 CharSet(如 Unicode 占 2 字节,Ansi 占 1 字节)来计算它转化成 C 语言格式后的大小。

4. 流程图:Marshal.SizeOf 如何在运行时工作

5. sizeof vs Marshal.SizeOf

它位于 System.Runtime.InteropServices 命名空间下。只要你安装了 .NET SDK,就可以直接通过 using 引用它。

  • 本质区别
    • sizeof:**编译时(Compile-time)**指令。它直接硬编码数值到程序中,速度极快,但只能用于非托管类型。
    • Marshal.SizeOf:运行时(Runtime)方法。它通过反射(Reflection)去测量对象在内存布局(特别是转换为非托管格式后)的大小,支持 struct 实例,也能处理一些被装箱的对象,但性能开销比 sizeof
特性sizeofMarshal.SizeOf
性能极高(等同于直接写数字)一般(有运行时开销)
上下文要求涉及自定义结构需 unsafe无需 unsafe
灵活性只能用类型名,不能用变量名可以直接传入变量实例
主要目的算偏移量、算指针位移调用 Win32 API、处理复杂封送

6. 易混淆

  • 句柄 (Handle/IntPtr):可以理解为系统资源的“代号”或“门票”。在 64 位系统下,它的大小等同于 long(8 字节)。
  • 封送 (Marshaling):将 C# 中的对象搬运到内存缓冲区,并转换成 C/C++ 能看懂的格式的过程。
  • StructLayout (LayoutKind.Sequential):强制要求编译器按照你在代码里写字段的顺序来排列内存,不要为了优化而打乱顺序,否则 Windows 系统会读错数据。
  • 非托管类型 (Unmanaged Type):指不需要垃圾回收器(GC)管理的类型。包括 sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, 枚举, 以及只包含这些类型的结构体。
  • 内存对齐 (Memory Alignment):CPU 读取内存时不是一个字节一个字节读的,而是以“块”(如 4 或 8 字节)为单位。为了提高读取速度,编译器会在数据结构之间插入一些无用的空字节,使数据起始地址落在块的边界上。
  • 填充 (Padding):内存对齐过程中自动添加的空闲字节。
  • unsafe 上下文:C# 默认是内存安全的,不允许直接操作地址。通过 unsafe 关键字,你可以在特定区域内像 C 语言一样直接操作指针和查看原始内存布局。

到此这篇关于C# 中的 sizeof 与非托管类型约束全解析的文章就介绍到这了,更多相关C# sizeof 与非托管类型约束内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C# wpf使用DockPanel实现制作截屏框

    C# wpf使用DockPanel实现制作截屏框

    做桌面客户端的时候有时需要实现截屏功能,能够在界面上框选截屏,本文就来为大家介绍一下wpf如何使用DockPanel制作截屏框吧,感兴趣的可以了解下
    2023-09-09
  • Unity Shader实现径向模糊效果

    Unity Shader实现径向模糊效果

    这篇文章主要为大家详细介绍了Unity Shader实现径向模糊效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 使用C#在.NET中实现高效提取PDF文档中的图片

    使用C#在.NET中实现高效提取PDF文档中的图片

    在文档处理、内容分析、数据迁移或自动化测试等场景中,开发者常常需要从 PDF 文件中提取嵌入的图像资源,本文将详细介绍如何使用 C# 从 PDF 中提取所有图片,并对比主流方案,感兴趣的小伙伴可以了解下
    2026-01-01
  • C#连接ClickHouse数据库的步骤指南

    C#连接ClickHouse数据库的步骤指南

    在 C# 中连接 ClickHouse 数据库,您可以使用 ClickHouse.Client 库,这个库提供了对 ClickHouse 数据库的高效访问,以下是详细的步骤指南,帮助您在 C# 项目中连接和操作 ClickHouse 数据库,需要的朋友可以参考下
    2024-12-12
  • Unity中C#和Java的相互调用实例代码

    Unity中C#和Java的相互调用实例代码

    在unity中接入sdk或者定制一些功能时,需要调用系统接口。安卓手机实际操作中,也就是Unity与android相互调用。我们在Unity中使用c#,android中使用java。
    2018-02-02
  • C# wpf 实现窗口任意区域点击拖动

    C# wpf 实现窗口任意区域点击拖动

    在wpf要实现此功能简单形式还是比较容易的,但是有一些细节需要专门处理,比如与按钮的点击事件冲突问题,解决事件冲突问题后拖动的灵敏度,可复用性等,这篇文章主要介绍了C# wpf 实现窗口任意区域点击拖动,需要的朋友可以参考下
    2024-03-03
  • C#实现PDF文档自动化生成的开发实战

    C#实现PDF文档自动化生成的开发实战

    在现代软件应用中,PDF文档因其跨平台、内容固定性强以及易于分享的特性,扮演着不可或缺的角色,本文将深入探讨如何利用C#强大的能力,结合一款功能丰富的PDF处理库,实现PDF文档的自动化生成,需要的朋友可以参考下
    2026-01-01
  • C#控制台程序中处理2个关闭事件的代码实例

    C#控制台程序中处理2个关闭事件的代码实例

    这篇文章主要介绍了C#控制台程序中处理2个关闭事件的代码实例,本文中的2个关闭事件是指Ctrl+C事件和窗口的关闭按钮事件,需要的朋友可以参考下
    2014-09-09
  • mvc开启gzip压缩示例分享

    mvc开启gzip压缩示例分享

    这篇文章主要介绍了mvc开启gzip压缩示例,需要的朋友可以参考下
    2014-03-03
  • C# SQLite 高级功能详解(推荐)

    C# SQLite 高级功能详解(推荐)

    本文介绍C#中SQLite的高级应用,涵盖事务处理(ACID)、连接池、批量操作、异步编程及性能优化,强调数据安全与并发控制,适用于移动和桌面开发,感兴趣的朋友跟随小编一起看看吧
    2025-06-06

最新评论