Thiết lập so sánh giữa class và number c++ năm 2024

Stack memory: Quá trình cấp phát và thu hồi trong

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3 sẽ hoạt động theo kiểu LIFO, nghĩa là dữ liệu nào được cấp phát trước sẽ bị thu hồi sau, tại sao nó lại thế?

  • Do Stack được sinh ra để phục vụ cho quá trình gọi hàm: ví dụ hàm

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    6 gọi hàm

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    7, khi đó

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    6 bắt đầu trước

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    7 nhưng lại kết thúc sau, thì các dữ liệu trong hàm

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    6 sẽ được cấp phát trước, sau đó mới tới

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    7; còn khi thu hồi thì ngược lại, dữ liệu trong

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    7 bị thu hồi trước, sau đó tới

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    6

  // Allocate sau A()
  // Deallocate trước B()
  public void B() { 
      int b = 5;
  }
  // Allocate trước B()
  // Deallocate sau B()
  public void A() {
      int a = 3;
  }

  • Thế nên,

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    3 chỉ chứa dữ liệu phát sinh trong quá trình gọi hàm (aka

    public class MyClass { } public record MyRecord { } void HeapOnly() {

    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable  
    var onlyHeapClass = new MyClass();  
    var onlyHeapRecord = new MyRecord();  
    
    }

    5), các dữ liệu trong hàm sẽ bị thu hồi sau khi hàm kết thúc. Tuy nhiên, 1 chương trình phải phát sinh dữ liệu nằm ngoài scope của hàm aka

    public class MyClass { } public record MyRecord { } void HeapOnly() {

    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable  
    var onlyHeapClass = new MyClass();  
    var onlyHeapRecord = new MyRecord();  
    
    }

    6 (không bị thu hồi sau khi hàm kết thúc), từ đó

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 ra đời
  • Heap memory: Dữ liệu được cấp phát sẽ không có thứ tự xác định, để truy cập dữ liệu trong

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 cần phải thông qua địa chỉ (khi cấp phát xong thì hệ điều hành sẽ trả về địa chỉ này)
  • Mục đích của Heap là tạo ra các

    public class MyClass { } public record MyRecord { } void HeapOnly() {

    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable  
    var onlyHeapClass = new MyClass();  
    var onlyHeapRecord = new MyRecord();  
    
    }

    6, nên dữ liệu trên

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 sẽ không tự động thu hồi sau khi kết thúc hàm (quá trình thu hồi cần phải được chương trình tự thực hiện, tuy nhiên Garbage collector đã làm hộ chúng ta)

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

Hiệu năng (performance) và kích thước (size) là thứ đáng chú ý giữa

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3 và

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4!

  • public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    3 có kích thước nhỏ hơn

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 nhiều, do Stack chỉ cần chứa các local variables, trong khi đó Heap cần phải chứa tất cả dữ liệu "sống" trong chương trình
  • Hiệu năng của

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    3 (cấp phát/thu hồi/truy xuất) cao hơn

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 rất nhiều, điều này hiển nhiên do các hàm sẽ được gọi rất nhiều lần trong chương trình
  • Thêm vào đó, Garbage collector là thành phần ảnh hưởng lớn đến hiệu năng của chương trình, càng nhiều dữ liệu trên

    public class AllocatedClass { } public void A() {

    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc  
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận  
    var c = new AllocatedClass();  
    
    }

    4 thì nó sẽ cần phải xử lý nhiều hơn.

Vậy bài học rút ra là... Hãy tận dụng Stack, và hạn chế Heap nhiều nhất có thể đối với các giá trị tạm thời

Value type và Reference type

Trong C# có 2 loại dữ liệu là Value type (managed data types) và Reference type (unmanaged data types), mỗi loại sẽ đại diện cho 1 số kiểu dữ liệu

  • Reference type: nó chính là class (dạo gần đây còn có thêm record), các kiểu dữ liệu khi khai báo dưới dạng

    public struct StackOnlyStruct { } void StackOnly() {

    int stackOnlyInt = 5;  
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);  
    
    } void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference  
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly  
    
    {
    var stackOnlyStruct = new StackOnlyStruct();  
    
    }

    8 thì sẽ luôn luôn nằm trên Heap
  • Value type: bao gồm các kiểu dữ liệu các bạn thường thấy

    public struct StackOnlyStruct { } void StackOnly() {

    int stackOnlyInt = 5;  
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);  
    
    } void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference  
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly  
    
    {
    var stackOnlyStruct = new StackOnlyStruct();  
    
    }

    9,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    0,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    1,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    2,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    3,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    4,

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    5... (

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    6 là

    public struct StackOnlyStruct {

    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;  
    
    } void StackOnly() {
    var stackOnlyStruct = new StackOnlyStruct {  
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()  
    };  
    
    }

    7 nhé), các dữ liệu này có thể nằm trên Heap hay Stack tùy vào cách sử dụng

    Các ảnh chụp dưới đây mình sử dụng dotMemory của Rider để thống kê

Reference type

Như đã nói,

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

7 và

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

9 sẽ luôn nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4 cho dù bạn có khai báo là nó là

public class MyClass { }
public record MyRecord { }
void HeapOnly()
{
    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable
    var onlyHeapClass = new MyClass();
    var onlyHeapRecord = new MyRecord();
}

5 hay

public class MyClass { }
public record MyRecord { }
void HeapOnly()
{
    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable
    var onlyHeapClass = new MyClass();
    var onlyHeapRecord = new MyRecord();
}

6

public class MyClass { }
public record MyRecord { }
void HeapOnly()
{
    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable
    var onlyHeapClass = new MyClass();
    var onlyHeapRecord = new MyRecord();
}

Khi chạy hàm

public class MyClass {
    public StackOrHeapStruct AStructFieldWillBeOnHeapToo;
    public int IntAlso;
}
public struct StackOrHeapStruct {
}
void HeapOrStackStruct() {
    // StackOrHeapStruct là Value type nên nằm trên Stack
    var onlyStack = new StackOrHeapStruct();
    // Lúc này AStructFieldWillBeOnHeapToo sẽ nằm trên Heap do là 1 field của class
    // Dù cho nó có là Value type đi chăng nữa
    var myClass = new MyClass {
        AStructFieldWillBeOnHeapToo = new StackOrHeapStruct(),
        IntAlso = 1
    };
}

3 sẽ cho thấy có 2 dữ liệu được cấp phát trên Heap

Value type

Value type (

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

9,

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

1,

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

5,

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

  1. sẽ nằm trên Stack nếu nó là

public class MyClass { }
public record MyRecord { }
void HeapOnly()
{
    // Class/Record instance sẽ luôn nằm trên Heap dù có là local variable
    var onlyHeapClass = new MyClass();
    var onlyHeapRecord = new MyRecord();
}

5 của 1 hàm.

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

Ta có thể thấy khi gọi hàm

public class MyClass {
    public StackOrHeapStruct AStructFieldWillBeOnHeapToo;
    public int IntAlso;
}
public struct StackOrHeapStruct {
}
void HeapOrStackStruct() {
    // StackOrHeapStruct là Value type nên nằm trên Stack
    var onlyStack = new StackOrHeapStruct();
    // Lúc này AStructFieldWillBeOnHeapToo sẽ nằm trên Heap do là 1 field của class
    // Dù cho nó có là Value type đi chăng nữa
    var myClass = new MyClass {
        AStructFieldWillBeOnHeapToo = new StackOrHeapStruct(),
        IntAlso = 1
    };
}

9 sẽ không cấp phát trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4 do đều sử dụng các

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

1 (hình dưới cho thấy không có instance nào của

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

2 trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

  1. Cần phải chú ý các

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

1 khi được gửi vào các hàm sẽ bị copy vào hàm (tự động tạo 1 biến mới tương tự trong hàm), để tránh quá trình này cần sử dụng ref (Pass by reference) hay in (Tương tự ref nhưng readonly) (quá trình này gọi là

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

5, bài sau mình sẽ hướng dẫn cách hạn chế nó)


Nếu

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

5 có chứa

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

7 thì cũng chỉ có

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

7 đó cấp phát trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4, còn

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

5 vẫn trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3 (và giữ địa chỉ của class instance nằm trong

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4)

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

Có thể thấy chỉ có

public struct Struct {}
public void StackAllocSpan() {
  // Cần phải khai báo kích thước của span
  // T phải là Value type
  Span<int> notPlayWithHeapIntSpan = stackalloc int[3];
  notPlayWithHeapIntSpan[0] = 0;
  Span<Struct> notPlayWithHeapStrutSpan = stackalloc Struct[3];
}

3 là trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4 còn

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

2 vẫn trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3

Value type sẽ trên Heap nếu nó nằm trong 1 Reference type

Khi một

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

1 trở thành field của

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

8 thì nó sẽ nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4, điều này tất nhiên, do nó cần phải "sống" cùng với instance này (không thể bị thu hồi khi kết thúc hàm)

public class MyClass {
    public StackOrHeapStruct AStructFieldWillBeOnHeapToo;
    public int IntAlso;
}
public struct StackOrHeapStruct {
}
void HeapOrStackStruct() {
    // StackOrHeapStruct là Value type nên nằm trên Stack
    var onlyStack = new StackOrHeapStruct();
    // Lúc này AStructFieldWillBeOnHeapToo sẽ nằm trên Heap do là 1 field của class
    // Dù cho nó có là Value type đi chăng nữa
    var myClass = new MyClass {
        AStructFieldWillBeOnHeapToo = new StackOrHeapStruct(),
        IntAlso = 1
    };
}

Ta kiểm tra

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4 của chương trình, sẽ thấy không chỉ

public struct Struct {}
public void StackAllocSpan() {
  // Cần phải khai báo kích thước của span
  // T phải là Value type
  Span<int> notPlayWithHeapIntSpan = stackalloc int[3];
  notPlayWithHeapIntSpan[0] = 0;
  Span<Struct> notPlayWithHeapStrutSpan = stackalloc Struct[3];
}

3 mà còn có

public class MyStruct_1 {
  public int MyInt;
  public MyClass MyClass;
}
public class MyClass {
  public MyStruct_2 MyFieldStruct_2;
}
public class MyStruct_2 {
}
public void MyFunc() {
  var myStruct_1 = new MyStruct_1 {
    MyInt = 1,
    MyClass = new MyClass() {
      MyFieldStruct_2 = new MyStruct_2()
    }
  };
}

2 và

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

9 được cấp phát chung

ref struct sẽ đảm bảo 1 struct luôn nằm trên Stack

Thỉnh thoảng bạn sẽ thấy có kiểu khai báo

public class MyStruct_1 {
  public int MyInt;
  public MyClass MyClass;
}
public class MyClass {
  public MyStruct_2 MyFieldStruct_2;
}
public class MyStruct_2 {
}
public void MyFunc() {
  var myStruct_1 = new MyStruct_1 {
    MyInt = 1,
    MyClass = new MyClass() {
      MyFieldStruct_2 = new MyStruct_2()
    }
  };
}

4, thật ra nó không có gì đặc biệt hết, đây chỉ là khai báo đảm bảo

public struct StackOnlyStruct
{
    public MyClass AReferenceDoesNotMakeTheStructStayOnHeap;
}
void StackOnly()
{
    var stackOnlyStruct = new StackOnlyStruct {
      AReferenceDoesNotMakeTheStructStayOnHeap = new MyClass()
    };
}

5 này không thể là field của 1

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

8 (để không thể nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4)

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

Đoạn code bên trên sẽ không thể

public class MyStruct_1 {
  public int MyInt;
  public MyClass MyClass;
}
public class MyClass {
  public MyStruct_2 MyFieldStruct_2;
}
public class MyStruct_2 {
}
public void MyFunc() {
  var myStruct_1 = new MyStruct_1 {
    MyInt = 1,
    MyClass = new MyClass() {
      MyFieldStruct_2 = new MyStruct_2()
    }
  };
}

8, do

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

2 là 1 `Kĩ thuật lập trình`0

Cấp phát mảng trên Stack với Span<T>

Trước khi kết thúc, mình muốn giới thiệu đến mọi người 1 `Kĩ thuật lập trình`0 là Span<T>, thông thường nó được dùng để trỏ tới 1 mảng trong Heap (tương tự ArraySegment), chúng ta sẽ xem 1 cách dùng hay ho khác nữa Với kiểu dữ liệu tuần tự (linear sequence), thì bạn sẽ sử dụng `Kĩ thuật lập trình`2 hay `Kĩ thuật lập trình`3, nhưng cả 2 thằng này đều là class (

public struct StackOnlyStruct { }
void StackOnly() {
    int stackOnlyInt = 5;
    AnotherStackOnly(stackOnlyInt, ref stackOnlyInt, false);
}
void AnotherStackOnly(int valueParameterWillBeCopiedIntoTheFunction,
    ref int useRefModifierForPassingByReferenceToAvoidCopying, //Pass by reference
    in bool useInModifierForPassingByReferenceAndReadonly)  //Pass by reference and readonly
{
    var stackOnlyStruct = new StackOnlyStruct();
}

  1. và sẽ nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4 Nếu muốn sử dụng mảng trên Stack, bạn cần kết hợp `Kĩ thuật lập trình`6 và `Kĩ thuật lập trình`7 (chú ý `Kĩ thuật lập trình`8 phải là

public ref struct StackOnlyStruct {}
public class MyClass {
  public StackOnlyStruct AReferenceTypeCanNotEmbedARefStructField;
}

1)

public struct Struct {}
public void StackAllocSpan() {
  // Cần phải khai báo kích thước của span
  // T phải là Value type
  Span<int> notPlayWithHeapIntSpan = stackalloc int[3];
  notPlayWithHeapIntSpan[0] = 0;
  Span<Struct> notPlayWithHeapStrutSpan = stackalloc Struct[3];
}

Khi gọi hàm

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

00 sẽ không cần cấp phát trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4. Tuy nhiên cần phải chú ý!

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3 có kích thước nhỏ, khi sử dụng

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

03 thì các phần tử trong

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

03 cũng nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

3 (thay vì chỉ 1 con trỏ tới dữ liệu như mảng thông thường), do đó việc cấp phát Span quá lớn có thể dẫn đến StackOverflow Sử dụng `Kĩ thuật lập trình`6 sẽ đem lại hiệu năng tốt hơn rất nhiều, tuy nhiên phải lưu ý

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

07 chỉ được sử dụng trong scope của hàm, không đươc

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

08 hay

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

09 nó ra , ngoài ra các

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

10 method cũng không cho phép

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

03

Kết luận

Vậy bạn đã có câu trả lời cho câu hỏi ban đầu rồi đúng không?

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

12 sẽ luôn trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4, trong khi đó

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

14 sẽ còn tùy vào tình huống sử dụng Hãy thử làm 1 quiz nho nhỏ để kiểm tra kiến thức nãy giờ nhé!

public class MyStruct_1 {
  public int MyInt;
  public MyClass MyClass;
}
public class MyClass {
  public MyStruct_2 MyFieldStruct_2;
}
public class MyStruct_2 {
}
public void MyFunc() {
  var myStruct_1 = new MyStruct_1 {
    MyInt = 1,
    MyClass = new MyClass() {
      MyFieldStruct_2 = new MyStruct_2()
    }
  };
}

Nếu ta chạy hàm

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

15 thì

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

16,

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

17,

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

18,

public struct Struct {}
public void StackAllocSpan() {
  // Cần phải khai báo kích thước của span
  // T phải là Value type
  Span<int> notPlayWithHeapIntSpan = stackalloc int[3];
  notPlayWithHeapIntSpan[0] = 0;
  Span<Struct> notPlayWithHeapStrutSpan = stackalloc Struct[3];
}

3 cái nào sẽ nằm trên

  public class AllocatedClass { }
  public void A() {
    // Biến "a" sẽ không bị tự động thu hồi sau khi A() kết thúc
    // Quá trình thu hồi sẽ do Garbage collector đảm nhận
    var c = new AllocatedClass();
  }

4?

Vậy là kết thúc bài đầu tiên trong series này. Ở post sau, mình sẽ đề cập đến 1 vấn đề khi sử dụng struct là Defensive copy và cách giải quyết