Hiểu phân bổ bộ nhớ trong Delphi

0
18


Gọi hàm “DoStackOverflow” một lần từ mã của bạn và bạn sẽ nhận được lỗi EStackOverflow do Delphi tạo ra với thông báo “tràn ngăn xếp”.


hàm DoStackOverflow : số nguyên; 

bắt đầu

kết quả := 1 + DoStackOverflow;

cuối cùng;

“Ngăn xếp” này là gì và tại sao lại có lỗi tràn ở đó khi sử dụng mã ở trên?

Vì vậy, hàm DoStackOverflow tự gọi nó theo cách đệ quy, không có “chiến lược thoát”, nó cứ quay và không bao giờ thoát.

Một cách khắc phục nhanh mà tôi sẽ làm là xóa lỗi rõ ràng mà bạn gặp phải và đảm bảo hàm tồn tại ở một thời điểm nào đó (để mã của bạn có thể tiếp tục chạy từ nơi bạn đã gọi hàm).

Bạn tiếp tục và không bao giờ nhìn lại, không quan tâm đến lỗi/ngoại lệ vì nó hiện đã được giải quyết.

Tuy nhiên, câu hỏi vẫn là: ngăn xếp này là gì và tại sao lại có tràn ?

Bộ nhớ trong các ứng dụng Delphi của bạn

Khi bạn bắt đầu lập trình trong Delphi, bạn có thể gặp lỗi như trên, bạn sẽ khắc phục và tiếp tục. Cái này liên quan đến cấp phát bộ nhớ. Hầu hết thời gian bạn sẽ không quan tâm đến việc cấp phát bộ nhớ miễn là bạn giải phóng những gì bạn tạo ra .

Khi bạn có nhiều kinh nghiệm hơn với Delphi, bạn bắt đầu tạo các lớp của riêng mình, khởi tạo chúng, lo lắng về việc quản lý bộ nhớ, v.v.

Bạn sẽ đến điểm mà bạn sẽ đọc, trong Trợ giúp, đại loại như “Các biến cục bộ (được khai báo bên trong các thủ tục và hàm) nằm trong ngăn xếp của ứng dụng .” các lớp cũng là loại tham chiếu nên chúng không được sao chép vào phân bổ, chúng được chuyển theo tham chiếu và phân bổ trên heap .

Vậy “stack” là gì và “heap” là gì?

đống so với đống

Khi chạy ứng dụng của bạn trên Windows , có ba vùng trong bộ nhớ nơi ứng dụng của bạn lưu trữ dữ liệu: bộ nhớ chung, heap và ngăn xếp.

Các biến toàn cầu (giá trị/dữ liệu của chúng) được lưu trữ trong bộ nhớ chung. Bộ nhớ cho các biến toàn cục được ứng dụng dành riêng khi chương trình bắt đầu và vẫn được cấp phát cho đến khi chương trình kết thúc. Bộ nhớ cho các biến toàn cục được gọi là “phân đoạn dữ liệu”.

Do bộ nhớ toàn cục chỉ được cấp phát một lần và được giải phóng khi kết thúc chương trình nên chúng tôi không quan tâm đến nó trong bài viết này.

Ngăn xếp và đống là nơi diễn ra quá trình cấp phát bộ nhớ động: khi bạn tạo một biến cho một hàm, khi bạn tạo một thể hiện của một lớp, khi bạn truyền tham số cho một hàm và sử dụng/truyền giá trị kết quả của nó.

Ngăn xếp là gì?

Khi bạn khai báo một biến bên trong một hàm, bộ nhớ cần thiết để chứa biến đó được cấp phát từ ngăn xếp. Bạn chỉ cần viết “var x: integer”, sử dụng “x” trong hàm của mình và khi hàm thoát, bạn không cần quan tâm đến việc cấp phát hay giải phóng bộ nhớ. Khi biến nằm ngoài phạm vi (mã thoát khỏi chức năng), bộ nhớ được sử dụng trên ngăn xếp sẽ được giải phóng.

Bộ nhớ heap được cấp phát động bằng phương pháp LIFO (“vào sau, ra trước”).

Trong các chương trình Delphi , bộ nhớ ngăn xếp được sử dụng bởi

  • Các biến thường trình cục bộ (phương thức, thủ tục, hàm).
  • Các tham số định kỳ và các kiểu trả về.
  • Các lệnh gọi hàm Windows API .
  • Bản ghi (đây là lý do tại sao bạn không cần phải khởi tạo một loại bản ghi một cách rõ ràng).

Bạn không cần phải giải phóng bộ nhớ trên ngăn xếp một cách rõ ràng, vì bộ nhớ được cấp phát tự động và kỳ diệu khi, chẳng hạn như khi bạn khai báo một biến cục bộ cho một hàm. Khi chức năng kết thúc (đôi khi còn sớm hơn do tối ưu hóa trình biên dịch Delphi), bộ nhớ của biến sẽ tự động được giải phóng bằng phép thuật.

Theo mặc định, kích thước bộ nhớ heap đủ lớn cho các chương trình Delphi (phức tạp như chúng) của bạn. Các giá trị “Kích thước ngăn xếp tối đa” và “Kích thước ngăn xếp tối thiểu” trong tùy chọn Trình liên kết cho dự án của bạn chỉ định các giá trị mặc định; ở mức 99,99%, bạn sẽ không cần sửa đổi điều này.

Hãy nghĩ về một ngăn xếp như một ngăn xếp các khối bộ nhớ. Khi bạn khai báo/sử dụng một biến cục bộ, trình quản lý bộ nhớ của Delphi sẽ lấy khối ở trên cùng, sử dụng nó và khi không cần nữa, hãy đẩy nó trở lại ngăn xếp.

Bằng cách sử dụng bộ nhớ biến cục bộ ngăn xếp, các biến cục bộ không được khởi tạo khi chúng được khai báo. Khai báo một biến “var x: integer” trong hàm nào đó và chỉ cố gắng đọc giá trị khi bạn nhập hàm; x sẽ có một số giá trị khác không “lạ”. Vì vậy, hãy luôn khởi tạo (hoặc đặt giá trị) cho các biến cục bộ của bạn trước khi đọc giá trị của chúng.

Do LIFO, các thao tác ngăn xếp (cấp phát bộ nhớ) diễn ra nhanh chóng vì chỉ cần một vài thao tác (đẩy, bật) để quản lý ngăn xếp.

đống là gì?

Heap là một vùng bộ nhớ trong đó bộ nhớ được cấp phát động được lưu trữ. Khi bạn khởi tạo một lớp, bộ nhớ được cấp phát từ heap.

Trong các chương trình Delphi, bộ nhớ heap được sử dụng bởi/khi

  • Tạo một thể hiện của một lớp.
  • Tạo và thay đổi kích thước mảng động.
  • Cấp phát bộ nhớ rõ ràng bằng cách sử dụng GetMem, FreeMem, New và Dispose().
  • Sử dụng các chuỗi, biến thể, giao diện ANSI/wide/Unicode (được quản lý tự động bởi Delphi).

Bộ nhớ heap không có thiết kế tốt, nơi sẽ có một số thứ tự trong việc phân bổ các khối bộ nhớ. Đống trông giống như một hộp bi. Việc cấp phát bộ nhớ của heap là ngẫu nhiên, một khối ở đây hơn một khối ở đó. Do đó, các thao tác trên đống chậm hơn một chút so với các thao tác trên ngăn xếp.

Khi bạn yêu cầu một khối bộ nhớ mới (nghĩa là tạo một thể hiện của một lớp), trình quản lý bộ nhớ của Delphi sẽ đảm nhận việc này cho bạn: bạn sẽ nhận được một khối bộ nhớ mới hoặc một khối được sử dụng và loại bỏ.

Heap bao gồm tất cả bộ nhớ ảo ( RAM và dung lượng ổ đĩa ).

Cấp phát bộ nhớ thủ công

Bây giờ mọi thứ liên quan đến bộ nhớ đã rõ ràng, bạn có thể (trong hầu hết các trường hợp) bỏ qua phần trên một cách an toàn và chỉ cần tiếp tục viết các chương trình Delphi như bạn đã làm ngày hôm qua.

Tất nhiên, bạn phải biết khi nào và làm thế nào để cấp phát/giải phóng bộ nhớ theo cách thủ công.

“EStackOverflow” (từ đầu bài viết) đã được nêu ra vì với mỗi lần gọi DoStackOverflow, một phân đoạn bộ nhớ mới của ngăn xếp đã được sử dụng và ngăn xếp có các giới hạn. Đơn giản vậy thôi.