Cấp phát bộ nhớ động là một kỹ thuật được sử dụng để cấp phát vùng nhớ cho chương trình tại thời điểm chương trình đang chạy. Trong ngôn ngữ lập trình C++, chúng ta có thể sử dụng toán tử new
để yêu cầu cấp phát vùng nhớ trên Heap và toán tử delete
để trả lại vùng nhớ đã được cấp phát.
Chức năng của các phân vùng trong bộ nhớ ảo
Có một số phân vùng quan trọng trên bộ nhớ ảo mà chúng ta cần hiểu:
- Code Segment: Là nơi chứa mã lệnh đã được biên dịch của các chương trình máy tính. Mã lệnh trong phân vùng này sẽ được chuyển đến CPU xử lý khi cần thiết.
- Data Segment: Là phân vùng được sử dụng để khởi tạo giá trị cho các biến kiểu static và biến toàn cục của các chương trình.
- .bss Segment: Cũng được sử dụng để lưu trữ các biến kiểu static và biến toàn cục, nhưng chưa được khởi tạo giá trị cụ thể.
- Heap Segment: Là vùng nhớ được sử dụng để cấp phát bộ nhớ cho chương trình thông qua kỹ thuật cấp phát bộ nhớ động. Phân vùng này có dung lượng lưu trữ lớn nhất trong các phân vùng.
- Call Stack Segment: Là nơi để cấp phát bộ nhớ cho tham số của các hàm và biến cục bộ của chương trình. Vùng nhớ này được xây dựng dựa trên cấu trúc dữ liệu Stack.
Cấp phát bộ nhớ đối với biến toàn cục và biến cục bộ
Đối với việc cấp phát bộ nhớ đối với biến toàn cục và biến cục bộ, chúng ta sử dụng hai kiểu khai báo khác nhau.
- Cấp phát bộ nhớ tĩnh (Static memory allocation): Được sử dụng cho biến static và biến toàn cục. Vùng nhớ của các biến này được cấp phát ngay khi chạy chương trình và cấp phát trên vùng nhớ Data hoặc .bss. Kích thước của vùng nhớ được cấp phát phải được cung cấp tại thời điểm biên dịch chương trình.
- Cấp phát bộ nhớ tự động (Automatic memory allocation): Được sử dụng để cấp phát vùng nhớ cho biến cục bộ và tham số của hàm. Bộ nhớ được cấp phát tại thời điểm chương trình đang chạy khi chương trình đi vào một khối lệnh. Kích thước vùng cần cấp phát cũng phải được cung cấp rõ ràng.
Vấn đề tràn bộ nhớ stack (Stack overflow)
Phân vùng Stack được đặt tại vùng có địa chỉ cao nhất trong bộ nhớ ảo và có dung lượng hạn chế. Khi chúng ta yêu cầu cấp phát vùng nhớ vượt quá dung lượng của Stack, chương trình sẽ phát sinh lỗi tràn bộ nhớ Stack.
Để khắc phục vấn đề này, chúng ta có thể sử dụng kỹ thuật cấp phát bộ nhớ động.
Kỹ thuật cấp phát bộ nhớ động
Cấp phát bộ nhớ động là một giải pháp để cấp phát vùng nhớ cho chương trình tại thời điểm chương trình đang chạy. Kỹ thuật này sử dụng phân vùng Heap để lưu trữ vùng nhớ được cấp phát. Vùng nhớ trên Heap có dung lượng lớn nhất trong bộ nhớ ảo và không phụ thuộc vào hệ điều hành.
Để thực hiện cấp phát bộ nhớ động, chúng ta cần làm hai bước:
- Yêu cầu cấp phát vùng nhớ trên Heap bằng toán tử
new
. - Lưu trữ địa chỉ của vùng nhớ được cấp phát bằng con trỏ.
Toán tử new
được sử dụng để yêu cầu cấp phát vùng nhớ trên Heap. Sau khi vùng nhớ được cấp phát, toán tử new
sẽ trả về một con trỏ chứa địa chỉ của vùng nhớ. Để trả lại vùng nhớ đã cấp phát, chúng ta sử dụng toán tử delete
.
Đối với việc cấp phát động cho mảng một chiều, chúng ta cần sử dụng cặp toán tử []
sau toán tử new
và delete
.
Ví dụ:
int* p_arr = new int[10]; // Cấp phát động một mảng int gồm 10 phần tử trên Heap.
// Sử dụng và gán giá trị cho các phần tử của mảng.
for (int i = 0; i < 10; ++i)
cin >> p_arr[i];
// Trả lại vùng nhớ đã cấp phát.
delete[] p_arr;
Kỹ thuật cấp phát bộ nhớ động giúp chúng ta kiểm soát được việc sử dụng vùng nhớ và tránh tràn bộ nhớ Stack. Tuy nhiên, chúng ta cũng cần lưu ý vấn đề "con trỏ bị treo" sau khi giải phóng vùng nhớ. Sau khi sử dụng toán tử delete
, con trỏ vẫn còn trỏ vào địa chỉ đã được giải phóng, và việc sử dụng toán tử *
cho con trỏ này sẽ gây lỗi không xác định.
Để tránh "con trỏ bị treo", chúng ta cần đặc biệt chú ý và không sử dụng con trỏ sau khi đã giải phóng vùng nhớ.
Tổng kết
Trong bài viết này, chúng ta đã tìm hiểu về cấp phát bộ nhớ động và các phân vùng trên bộ nhớ ảo. Chúng ta cũng đã biết cách sử dụng kỹ thuật cấp phát bộ nhớ động để quản lý vùng nhớ trên Heap và tránh vấn đề tràn bộ nhớ Stack.