Sự khác biệt giữa Stack và Heap trong lập trình c#

Như các bạn đã biết c# là một ngôn ngữ đa năng, mạnh mẽ và hướng đối tượng. Trong lập trình c# quản lý về cấp phát bộ nhớ và dọn dẹp rác được thực hiện một cách tự động. Tuy nhiên đối với các bạn mới học lập trình c# thì cần hiểu quan tâm đến cơ chế hoạt động của chúng ra sao. Bên cạnh đó những người chưa rõ cũng nên đọc bài viết để hiểu được rõ cách thức hoạt động của một số biến trong lập trình. Bài viết này tôi sẽ khái quát về Stack và Heap các loại biến và giải thích cách hoạt động của chúng.

.Net framwork lưu trữ tất cả các phần tử của mình ở 2 nơi trong bộ nhớ đó là Stack và Heap. Cả Stack và Heap đều có ý nghĩa rất quan trọng trong việc thi hành code. Chúng được đặt trong bộ nhớ điều hành trên máy và chứa những phần của thông tin và chúng ta cần để vận hành.

Sự khác nhau giữa Stack và Heap.

Các bạn hãy hình dùng Stack như một tập hợp các ngăn xếp mà ngăn đầu tiên nằm ở trên cùng. Chúng ta chỉ có thể làm việc được với ngăn xếp trên cùng ấy. Sau khi làm việc với ngăn trên cùng chúng ta phải “vứt” nó đi thì mới làm việc được với ngăn xếp tiếp theo. Còn Heap cũng tương tự như Stack nhưng mục đích sử dụng của Heap là để lưu trữ thông tin chứ không phải lưu lại tất cả các lần thi hành lệnh như stack và tất cả thông tin được lưu trên Heap có thể được truy cập bất kì thời điểm nào. Không có sự phụ thuộc dữ liệu nào được phép truy cập như stack. Nếu Heap là một đống quần áo sạch sẽ trên giường mà bạn có thể thử bất cứ cái nào thì stack như một hộp chứa đồ mà bạn phải lấy cái trước ra rồi mới lấy được cái sau.

Hoạt động của Stack và Heap trong lập trình c#

Hình ảnh trên không thực sự đúng với những gì diễn ra trong bộ nhớ nhưng có thể cho bạn thấy sự khác biệt giứa Stack và Heap.

Stack có thể tự duy trì, có nghĩa là nó cơ bản có thể quản lý được bộ nhớ của nó, khi “hộp” đầu tiên không được sử dụng nó sẽ được vứt đi. Còn Heap lại khác, chúng ta phải quan tâm đến các dữ liệu dư thừa và việc giữ Heap được “sạch sẽ”.

Cái gì được lưu trong Stack và Heap?

Chúng ta có 4 thứ sẽ được lưu trữ trong Stack và Heap đó là : Tham trị, tham chiếu, con trỏ và các chỉ dẫn.

Các tham trị

Trong C# tất cả những biến được khai báo như sau là tham trị (Năm trong System. ValueType)

  • bool
  • byte
  • char
  • decimal
  • double
  • enum
  • float
  • int
  • long
  • sbyte
  • short
  • struct
  • uint
  • ulong
  • ushort

Tham chiếu

Các loại dưới đây là tham chiếu (và được thừa kế từ System.Object…, tất nhiên là ngoài trừ những đối tượng của chính System.Object)

  • class
  • interface
  • delegate
  • object
  • string

Con trỏ

Loại thứ 3 được lưu trữ trong bộ nhớ là con trỏ. Con trỏ được quản lý bởi Common Language Nó khác với biến tham chiếu ,biến tham chiếu thì có thể được truy cập bởi một con trỏ. Con trỏ chiếm một vị trí nào đó trong bộ nhớ và sẽ trỏ đến một ví trí khác. Con trỏ có thể truy cập đến mọi thứ bạn lưu trong stack và heap và giá trị của nó có thể là một địa chỉ nhớ hoặc rỗng.

Chỉ dẫn

Tôi sẽ đề cập đến chỉ dẫn ở phần sau của bài viết này.

Làm sao để biết cái gì được làm ở đâu?Chúng ta có 2 quy tắc sau:

Tham chiếu thì luôn được thực hiện trong Heap

Con trỏ và tham trị luôn được thực hiện ở nơi nó được định nghĩa. Điều này khá phức tạp, để hiểu được bạn cần hiểu thêm về cách làm việc của stack.

Stack như tôi đã giới thiệu, dùng để giữ lại các bước thực hiện khi bạn coding. Bạn có thể hình dung nó giống như trạng thái của một thread và mỗi một thread sẽ có một stack riêng cho nó. Khi coding bạn gọi một hàm thì lời gọi hàm và các tham số của hàm sẽ được lưu vào stack. Và chúng ta sẽ thao tác với các biến ở trong hàm nằm trên đầu stack. Các bạn hãy xem ví dụ sau để hiểu rõ hơn.

Hoạt động của Stack và Heap trong lập trình c#

Các bạn hãy nhìn vào hình vẽ. Biến kiểu int pValue được nằm trên cùng sau đó mới đến tên hàm AddFive().

Chú ý rẳng hình ảnh chỉ có tính chất minh họa.

Hoạt động của Stack trong lập trình c#

Tiếp đến, Thread thi hành method sẽ thực hiện theo nội dung của hàm AddFive() và một trình biên dịch JIT Sẽ được thực hiện nếu đây là lần đầu tiên chúng ta gọi đến nó. Nếu bạn chưa rõ JIT là gì bạn có thể tham khảo bài viết: Phân biệt các khái niệm trong .NET

Hoạt động của Stack trong lập trình c#

Và sau khi hàm đó được thực hiện chúng ta cần phải có bộ

nhớ để lưu biến kết quả và đó chính là một nơi trong stack.

Hoạt động của Stack trong lập trình c#

Khi hàm kết thúc kết quả sẽ được trả về và được lưu trong biến result.

Hoạt động của Stack trong lập trình c#

Và vùng nhớ trong stack sẽ được giải póng bằng cách đưa con trỏ đến một vùng nhớ khác nơi hàm AddFive() bắt đầu và chúng ta sẽ đi xuống hàm tiếp theo trong stack.

Hoạt động của Stack trong lập trình c#

Trong ví dụ này, biến “result” là một nơi trong stack. Như ta đã thấy, cứ lúc nào một biến trong method được khai báo thì nó sẽ được đặt vào stack

Tuy nhiên kiểu giá trị cũng được lưu trong Heap. Hãy nhớ quy tắc, Kiểu giá trị luôn đến nơi nó được khai báo? Vậy thì nếu một biến kiểu giá trị được khai báo ngoài hàm nhưng trong một kiểu tham chiếu nó sẽ nằm ở trong kiểu tham chiếu trên Heap.

Sau đây là ví dụ:

Chúng ta có lớp class MyInt đây là kiểu tham chiếu vì nó là một class

Như tôi đã nói, thread bắt đầu thi hành hàm và những tham số của nó sẽ được đặt vào trong stack của thread đó.

Hoạt động của Stack và Heap trong c#

Nó bắt đầu có sự khác biệt với ví dụ trước.

Hoạt động của Stack và Heap trong c#

Sau khi hàm AddFive() kết thúc, chúng ta sẽ dọn dẹp ….

Hoạt động của Stack và Heap trong c#

không có một con trỏ nào trỏ đến MyInt.

Hoạt động của Stack và Heap trong c#

Đây là lúc chúng ta cần đến bộ dọn dữ liệu rác . Mỗi lần chương trình của chúng ta gần vượt qua giới hạn bộ nhớ, chúng ta sẽ cần thêm không gian trong heap. Bộ dọn dữ liệu rác sẽ dừng tất cả các thread lại (a FULL STOP), tìm tất cả các đối tượng trong heap mà đang không được truy cập bởi chương trình chính và xóa nó đi. Bộ dọn dữ liệu rác cũng sẽ tổ chức lại các đối tượng để tạo không gian nhớ và điều chỉnh tất cả các con trỏ đến các đối tượng ở cả stack và Heap. Bạn có thể nghĩ rằng đây là một sự cản trở tới quá trình thực hiện chương trình, do đó việc sắp xếp dự liệu trong stack và Heap là rất cần thiết để có một chương trình tối ưu.

Khi chúng ta sử dụng kiểu tham chiếu, chúng ta phải phân chia con trỏ đến kiểu, không phải chỉ quan tâm đến riêng kiểu tham chiếu tuy nhiên khi chúng ta sử dụng kiểu giá trị chúng ta chỉ cần quan tâm đến bản thân kiểu đó. Nghe có vẻ khó hiểu? Hãy nghiên cứu ví dụ sau:

Nếu chúng ta thực thi hàm sau:

Chúng ta sẽ nhận về giá trị 3, khá là đơn giản phải không?

Tuy nhiên nếu chúng ta sử dụng MyInt class từ trước

sau đó chúng ta thực hiện method sau:

Chúng ta sẽ nhận được kết quả là 4.

Tại sao lại như vậy?

ở ví dụ đầu tiên mọi thư như được sắp sẵn như sau:

Hoạt động của Stack và Heap trong c#

ở ví dụ sau chúng ta không nhận về 3 bời vì cả 2 biến x và y đều trỏ đến 2 đối tượng trên Heap.

Hoạt động của Stack và Heap trong c#

Hi vọng bài viết này sẽ giúp bạn hiểu một cách cơ bản nhất về sự khác biệt giữa biến kiểu giá trị và kiểu tham chiếu trong lập trình c#, về con trỏ và cách sử dụng của nó. Ở phần sau tôi sẽ giới thiệu thêm về cách quản lý bộ nhớ và đặc biệt là các tham số trong một method. Nếu bạn thấy bài viết ý nghĩa với bạn hãy vote cho chúng tôi để có thêm động lực tìm hiểu, viết và cung cấp kiến thức hữu ích cho các bạn. Chúc các bạn nghiên cứu và học tập ngôn ngữ c# và những khái niệm quan trọng của nó được tốt !

Nhận xét