C# sizeof 计算规则

冰封百度
English

1.sizeof的作用

我个人的理解:sizeof是用来获取非托管类型所占内存大小的操作符。

微软官方对sizeof的定义:

sizeof 运算符返回给定类型的变量所占用的字节数。 sizeof 运算符的参数必须是一个sizeof的名称,或是一个限定为非托管类型的类型参数。

《微软官方文档》
https://docs.microsoft.com/zh...

2.sizeof和Mashal.sizeof

首先要说的是sizeof和Marshal.SizeOf是有差异的。

C# 中的sizeof和Marshal.SizeOf都可以用来获取非托管类型的大小,但是功能上有差别,结果也略有差异。

sizeof在正常环境下只能用于预定义的非托管类型,如int、long等等。在unsafe环境下,sizeof可以被用于值类型,但是值类型中不可以有引用类型,否则C#编译器会报错。

Marshal.SizeOf则是获得该类型被Marshal(转换,通常翻译为列集,指数据从一种类型转换到另外一种类型)到对应的非托管类型的大小。和sizeof不同,Marshal.SizeOf允许用在含有引用类型的值类型上。

参考资料:《Marshal.SizeOf和sizeof的区别》

先看输出结果:

public struct Struct1 {

    public int Id;

}

public struct Struct2 {

    public int Id;
    public string Name;

}

// sizeof()测试 系统内置的基本类型sizeof是固定常量值,编译器自动替换,不是通过计算得到的 不需要用unsafe 自定义的需要unsafe下计算
private void SizeofTest() {
    Console.WriteLine(sizeof(bool));   // 1
    Console.WriteLine(sizeof(byte));   // 1
    Console.WriteLine(sizeof(sbyte));  // 1
    Console.WriteLine(sizeof(short));  // 2
    Console.WriteLine(sizeof(ushort)); // 2
    Console.WriteLine(sizeof(int));    // 4
    Console.WriteLine(sizeof(uint));   // 4
    Console.WriteLine(sizeof(long));   // 8
    Console.WriteLine(sizeof(ulong));  // 8
    Console.WriteLine(sizeof(char));   // 2
    Console.WriteLine(sizeof(float));  // 4
    Console.WriteLine(sizeof(double)); // 8

    //自定义struct
    unsafe {
        Console.WriteLine(sizeof(Struct1));// 4
        //Console.WriteLine(sizeof(Struct2));// 编译器报错 无法获取托管类型("Struct2")的地址和大小,或者声明指向他的指针
    }
}

// Mashal.SizeOf()测试
private void MarshalSizeofTest() {
    // using System.Runtime.InteropServices;
    Console.WriteLine(Marshal.SizeOf(typeof(bool)));   // 4 (sizeof是1)
    Console.WriteLine(Marshal.SizeOf(typeof(byte)));   // 1
    Console.WriteLine(Marshal.SizeOf(typeof(sbyte)));  // 1
    Console.WriteLine(Marshal.SizeOf(typeof(short)));  // 2
    Console.WriteLine(Marshal.SizeOf(typeof(ushort))); // 2
    Console.WriteLine(Marshal.SizeOf(typeof(int)));    // 4
    Console.WriteLine(Marshal.SizeOf(typeof(uint)));   // 4
    Console.WriteLine(Marshal.SizeOf(typeof(long)));   // 8
    Console.WriteLine(Marshal.SizeOf(typeof(ulong)));  // 8
    Console.WriteLine(Marshal.SizeOf(typeof(char)));   // 1 (sizeof是2)
    Console.WriteLine(Marshal.SizeOf(typeof(float)));  // 4
    Console.WriteLine(Marshal.SizeOf(typeof(double))); // 8

    // 自定义struct
    Console.WriteLine(Marshal.SizeOf(typeof(Struct1)));// 4
    Console.WriteLine(Marshal.SizeOf(typeof(Struct2)));// 8
} 

3.sizeof大小的计算

在计算之前,请看上面SizeofTest()方法内的输出结果,记住基本数据类型的大小,用来计算。

我个人根据输出结果总结了以下几个计算规则:

1.首先确定最小分配空间UnitSize

32位应用每一个单位分配的最大空间为32位即8字节。

找到struct成员中基本数据类型size最大的值,单元的size值在该maxSize值和8之间取最小。(int unitSize = Math.Min(maxSize, 8))

2.先分配一个单元空间, 从上往下按顺序装struct中的成员size。

3.每个类型占用空间的起始序号必须为0或该类型size的整数倍,空间不够就重新开一个单位空间。

比如:

bool占1字节,占用空间的起始序号可以为0,1,2,3,4...。

short占2字节,占用空间的起始序号可以为0,2,4 ....。

int占4字节,占用空间的起始序号可以为0,4....。

4.如果某个成员是自定义的值类型, maxSize需要遍历值类型成员中的基本数据类型size决定,不是由值类型的总size决定。

大概计算过程如下:

// 原文地址:https://www.cnblogs.com/zhangyukof/p/16159965.html
public struct Struct3 {
    public bool a;
    public int b;
    public short c;
}

// 以计算struct3 size为例:

// 1.先获取struct中 成员数据类型size最大的
int maxSize = 4; // bool = 1, int = 4, short = 2

// 2.单元空间大小取成员类型size和8之间的最小值
int unitSize = Math.Min(maxSize, 8); // 4

// 3.开始计算占用空间
(1) □ □ □ □   // 分配一个新单元 4字节

(2) ■ □ □ □   // 装入bool 占用一个字节

(3) ■ □ □ □   // 剩余空间序号不满足0或4的整数倍 不能装入int 重新分配一个单元
    □ □ □ □

(4) ■ □ □ □   // 装入int 从新单元的0位置开始占用4个字节
    ■ ■ ■ ■

(5) ■ □ □ □   // 分配一个新单元 4字节
    ■ ■ ■ ■
    □ □ □ □

(6) ■ □ □ □   // 装入short 占用2个字节
    ■ ■ ■ ■
    ■ ■ □ □

// 4.共计3个单元 3*unitSize = 12字节

4.成员顺序影响sizeof的大小

由于分配的规则3是每个类型占用空间的起始序号必须为0或该类型size的整数倍,空间不够就重新开一个单位空间。

重新分配一个新的单元从0位置开始装,所以成员的顺序有可能影响sizeof的大小。

比如把struct3中 b和c的顺序调换一下就变成了8字节

public struct Struct3 {
    public bool a;
    public short c;
    public int b;
}

(1) □ □ □ □   // 分配一个新单元 4字节

(2) ■ □ □ □   // 装入bool 占用一个字节

(3) ■ □ ■ ■   // 装入short 占用2个字节 (short起始序号为2)

(4) ■ □ ■ ■   // 分配一个新单元 4字节
    □ □ □ □

(5) ■ □ ■ ■   // 装入int 占用4个字节
    ■ ■ ■ ■

// 共计2个单元 2*unitSize = 8字节

没错,这是一个笔试考点。

5.自定义值类型影响sizeof的大小

再来一个难一点的,当有成员类型是自定义类型 并且总size超过8 看看结果如何:

计算自定义值类型Struct4 size大小:

public struct Struct4 {

    public bool a;    // size = 1
    public StructSize16 b;    // size = 16
    public int c;     // size = 4
    public short d;   // size = 2

}

public struct StructSize16 {

    public int a1; // size = 4
    public int a2;
    public int a3;
    public int a4;

}

如果按照以下方式计算:

int maxSize = 16; // StructSize16 = 16
int unitSize = Math.Min(maxSize, 8); // 8

1.■ □ □ □ □ □ □ □// 分配一个单元8字节 装入bool 占1字节

2.■ □ □ □ □ □ □ □ // 剩余空间的起始序号不满足StructSize16的需求 分配2个新单元 装入StructSize16 占16个字节
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ ■ ■ ■ ■
  
3.■ □ □ □ □ □ □ □ // 分配一个单元8字节 装入int 占4字节
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ □ □ □ □
  
4.■ □ □ □ □ □ □ □ // 装入short 占2字节
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ ■ ■ □ □
  
5.共计4个单位空间  4*unitSize = 32字节

结果就错了,因为成员遇到自定义类型要遍历自定义类型里的基本数据类型再确定maxSize

自定义值类型里面的成员有4个 都是int型 maxSize为4,这里要注意以下,正确计算方式应该如下:

int maxSize = 4; // bool = 1, short = 2, int = 4
int unitSize = Math.Min(maxSize, 8); // 4

1.■ □ □ □ // 分配一个单元4字节 装入bool 占1字节

2.■ □ □ □ // 剩余3字节的空间装不下StructSize16 分配4个新单元 装入StructSize16 占16个字节
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  
3.■ □ □ □ // 分配一个新单元4字节 装入int 占4字节
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■

4.■ □ □ □ // 分配一个新单元4字节 装入short 占2字节
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ ■ ■
  ■ ■ □ □
  
5.共计7个单位空间  7*unitSize = 28字节

这个计算结果与输出结果保持一致,都是28字节。

把自定义类型StructSize16换成decimal结果也是28字节,因为decimal里面也有4个int类型的成员,从结果看来,decimal没有被当成基本数据类型来看待,maxSize是4不是16。

最后再定义一个稍微复杂点的类型来测验一下计算规则是否正确:

public struct Student {

    public int num;
    public bool isAtSchool;
    public short age;
    public long id;
    public byte rank;

}

int maxSize = 8; // bool = 1, byte = 1, short = 2, int = 4, long = 8
int unitSize = Math.Min(maxSize, 8); // 8

1.□ □ □ □ □ □ □ □ // 分配一个单元8字节

2.■ ■ ■ ■ □ □ □ □ // 装入int 占4字节

3.■ ■ ■ ■ ■ □ □ □ // 装入bool 占1字节

4.■ ■ ■ ■ ■ □ ■ ■ // 装入short 占2字节 起始序号为6

5.■ ■ ■ ■ ■ □ ■ ■ // 分配一个新单位8字节
  □ □ □ □ □ □ □ □
  
6.■ ■ ■ ■ ■ □ ■ ■ // 装入long 占8字节
  ■ ■ ■ ■ ■ ■ ■ ■
  
7.■ ■ ■ ■ ■ □ ■ ■ // 新分配一个单位空间8字节
  ■ ■ ■ ■ ■ ■ ■ ■
  □ □ □ □ □ □ □ □
  
8.■ ■ ■ ■ ■ □ ■ ■ // 装入byte 占1字节
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ □ □ □ □ □ □ □
  
9.共计 3*8 = 24字节 // 计算结果与输出结果一致 都是24字节 计算正确

// 可以看到空间第三行首位单独占了一个字节,空余7个字节,
// 如果把第三行的数据移动到第一行空白的位置,那么就不需要第三行了,可以节省空间,试一下。
// 把Student中rank字段的位置移动到isAtSchool后面。

public struct Student {

    public int num;
    public bool isAtSchool;
    public byte rank;
    public short age;
    public long id;

}

// 这样内存占用就变成了:
■ ■ ■ ■ ■ ■ ■ ■
■ ■ ■ ■ ■ ■ ■ ■

// 理论上应该16个字节就够用了,运行测试代码输出一下结果,sizeof(Student)确实是16,检验成功。

总结:

sizeof()计算非托管类型的大小不是直接按照成员数据类型大小加起来的,涉及到一些特殊的计算规则,正常使用不需要记这些复杂的东西。

如果需要用到这部分知识了,应该涉及到性能优化或遇到考题了,记住基本数据类型的size就可以满足一般需求了。

struct内成员的顺序会影响sizeof大小 成员变量也会影响sizeof大小。没必要的变量不要定义为成员变量,private也会被计算进size内,成员尽量用数值size更小的定义。

最后再附赠一个查看自定义struct内存排布的方法:

我用的是Visual Studio软件,打开调试 > 窗口 > 内存 > 内存1。

断点设置在声明对象的位置,在中断的时候内存地址栏输入&对象名称。

给对象的成员逐个赋值成最大值,F10往下走就可以看到内存变化了。

至此,sizeof的计算过程告一段落。

PS:转载请标明原文地址:https://www.cnblogs.com/zhang...

参考资料:

《C# Marshal.SizeOf和sizeof的区别》
《C#sizeof用法》

阅读 577

冰封百度的学习笔记
程序生涯中的技术整理

Unity游戏程序员一枚。生命不息,学习不止。

193 声望
36 粉丝
0 条评论

Unity游戏程序员一枚。生命不息,学习不止。

193 声望
36 粉丝
文章目录
宣传栏