C# sizeof 計算規則

冰封百度發表於2022-04-27

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用法》

相關文章