Một biến muốn sử dụng được cần được khai báo. Khi chạy chương trình và gặp lệnh khai báo thì hệ điều hành sẽ cấp phát vùng nhớ cho biến. Có những cách cấp phát bộ nhớ là:
- Cấp phát bộ nhớ tĩnh (Static memory allocation)
- Cấp phát bộ nhớ tự động (Automatic memory allocation)
- Cấp phát bộ nhớ động (Dynamic memory allocation)
Bài này sẽ giúp các bạn hiểu rõ những cách cấp phát bộ nhớ này. Trong đó, cấp phát bộ nhớ động là một kỹ thuật lập trình rất hay nhưng cũng khá khó hiểu. Nhưng một khi đã hiểu và sử dụng thành thạo nó thì bạn sẽ dễ dàng quản lý bộ nhớ và tối ưu chương trình của mình.
1. Hiểu về những cách cấp phát bộ nhớ trong C++
Bộ nhớ ảo và bộ nhớ vật lý
Bộ nhớ vật lý là bộ nhớ RAM. Khi khai báo biến thì cần cấp phát vùng nhớ cho biến. Vùng nhớ của biến được xác định bởi địa chỉ duy nhất. Thực tế, địa chỉ này không phải là địa chỉ vật lý trên RAM mà là địa chỉ trên bộ nhớ ảo. Vậy bộ nhớ ảo là gì?
Bộ nhớ ảo là một kỹ thuật quản lý bộ nhớ được sự hỗ trợ của cả hệ điều hành và CPU. Địa chỉ ô nhớ được sử dụng bởi các chương trình phần mềm là địa chỉ ảo. Hệ điều hành sẽ quản lý những địa chỉ ảo này và có nhiệm vụ xác định địa chỉ ảo nào tương ứng với địa chỉ vật lý nào trên RAM.
Khi CPU cần xử lý dữ liệu theo yêu cầu của chương trình phần mềm, CPU sẽ tự động chuyển địa chỉ ảo sang địa chỉ vật lý để truy xuất dữ liệu. Đơn vị quản lý bộ nhớ (MMU) là một thiết bị phần cứng được tích hợp vào CPU để làm nhiệm vụ này.
Bộ nhớ ảo được sinh ra nhằm giúp các ứng dụng quản lý vùng bộ nhớ được chia sẻ, tăng độ an toàn cho các vùng nhớ và giúp chương trình sử dụng nhiều bộ nhớ hơn bộ nhớ vật lý hiện có.
Các vùng nhớ của một chương trình khi thực thi
Khi một chương trình được thực thi, những địa chỉ ảo dành cho chương trình đó được chia thành nhiều vùng nhớ khác nhau. Những vùng nhớ đó là:
Code segment là nơi lưu trữ các lệnh của chương trình đã được biên dịch. Các lệnh này có thể dưới dạng mã máy hoặc bytecode.
Data là nơi khởi tạo giá trị cho các biến kiểu static, biến toàn cục (global variable) của chương trình.
BSS dùng để lưu trữ các biến kiểu static, biến toàn cục (global variable) nhưng chưa được khởi tạo giá trị cụ thể.
Heap được sử dụng để cấp phát bộ nhớ thông qua kỹ thuật cấp phát bộ nhớ động (Dynamic memory allocation). Bộ nhớ sẽ được cấp phát khi chương trình đang thực thi (runtime).
Stack được dùng để cấp phát bộ nhớ thông qua các kỹ thuật:
- Cấp phát bộ nhớ tĩnh (Static memory allocation): cấp phát cho biến static và biến toàn cục, được cấp phát lúc biên dịch chương trình.
- Cấp phát bộ nhớ tự động (Automatic memory allocation): cấp phát vùng nhớ cho các biến cục bộ và các tham số của hàm, đượ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.
2. Cấp phát bộ nhớ động cho các biến cơ bản
Trong ngôn ngữ C++, các bạn có thể dùng toán tử new và delete để cấp phát và giải phóng bộ nhớ động cho các biến cơ bản. Nếu dùng ngôn ngữ C thì các bạn có các hàm malloc(), calloc(), realloc(), free() để cấp phát và giải phóng bộ nhớ động.
Cấp phát động cho biến với toán tử new
Chức năng của toán tử new: cấp phát bộ nhớ động. Toán tử này trả về một con trỏ trỏ tới địa chỉ ô nhớ đầu tiên của vùng nhớ vừa được cấp phát.
Cú pháp cấp phát bộ nhớ sử dụng new:
<tên biến con trỏ> = new <kiểu dữ liệu>;
Ví dụ cấp phát bộ nhớ động chứa một phần tử có kiểu int:
int *p;
p= new int;
Trong một số trường hợp, nhiều chương trình đang chạy và máy tính hết bộ nhớ. Lúc này, hệ điều hành không thể cấp phát động khi dùng toán tử new, một con trỏ NULL sẽ được trả về.
Nên kiểm tra xem con trỏ trả về bởi toán tử new có bằng NULL hay không:
int * a;
a = new int;
if (a == NULL) {
//thông báo hết bộ nhớ
};
Giải phóng bộ nhớ động của biến với toán tử delete
Bộ nhớ động đã được cấp phát nhưng khi không cần dùng nữa thì cần giải phóng bộ nhớ này. Sử dụng toán tử delete để giải phóng bộ nhớ động đã được cấp phát:
delete <tên biến con trỏ>;
Ví dụ:
int *p;
p= new int;
delete p;
Lưu ý: Giải phóng vùng nhớ 4 byte kiểu int rồi nhưng con trỏ p vẫn trỏ tới ô nhớ có địa chỉ 80024. Ta gọi là “con trỏ lạc”. Nếu sau đó lỡ thao tác với con trỏ p (*p) thì dữ liệu lấy ra được chỉ là giá trị rác hoặc sẽ bị lỗi. Để tránh con trỏ lạc thì gán con trỏ bằng NULL sau khi delete:
int *p;
p= new int;
delete p;
p = NULL;
Chương trình C++ minh họa cấp phát động cho biến
#include <iostream>
using namespace std;
int main() {
// declare an int pointer
int *pointInt;
// declare a float pointer
float *pointFloat;
// dynamically allocate memory
pointInt = new int;
pointFloat = new float;
// assigning value to the memory
*pointInt = 45;
*pointFloat = 45.45f;
cout << *pointInt << endl;
cout << *pointFloat << endl;
// deallocate the memory
delete pointInt;
delete pointFloat;
pointInt = NULL;
pointFloat = NULL;
system("pause");
}
Kết quả
45
45.45
3. Cấp phát bộ nhớ động cho mảng
Có thể cấp phát bộ nhớ động một dãy các vùng nhớ liên tục gồm nhiều phần tử, tức là một mảng:
<tên biến con trỏ> = new <kiểu dữ liệu>[<số phần tử>];
Tương tự, cũng có thể giải phóng vùng nhớ liên tục này:
delete[] <tên biến con trỏ>;
Chương trình C++ minh họa cấp phát động cho mảng
// C++ Program to store GPA of n number of students and display it
// where n is the number of students entered by the user
#include <iostream>
using namespace std;
int main() {
int num;
cout << "Enter total number of students: ";
cin >> num;
float *ptr;
// memory allocation of num number of floats
ptr = new float[num];
cout << "Enter GPA of students." << endl;
for (int i = 0; i < num; ++i) {
cout << "Student" << i + 1 << ": ";
cin >> *(ptr + i);
}
cout << "\nDisplaying GPA of students." << endl;
for (int i = 0; i < num; ++i) {
cout << "Student" << i + 1 << " :" << *(ptr + i) << endl;
}
// ptr memory is released
delete[] ptr;
ptr = NULL;
system("pause");
}
Kết quả
Enter total number of students: 5
Enter GPA of students.
Student1: 3.6
Student2: 5.9
Student3: 7.0
Student4: 8
Student5: 9.5
Displaying GPA of students.
Student1 :3.6
Student2 :5.9
Student3 :7
Student4 :8
Student5 :9.5
4. Cấp phát bộ nhớ động cho struct
Có thể cấp phát bộ nhớ động lưu trữ một kiểu dữ liệu cấu trúc:
<tên biến con trỏ> = new <kiểu dữ liệu>;
Tương tự, cũng có thể giải phóng bộ nhớ động của kiểu dữ liệu cấu trúc:
delete <tên biến con trỏ>;
Chương trình C++ minh họa cấp phát động cho struct
#include <iostream>
using namespace std;
struct POINT {
int x;
int y;
};
int main() {
//declare a struct POINT pointer
POINT* ptr;
//dynamically allocate memory
ptr = new POINT;
//input POINT
cout<<"Input point"<<endl;
cout<<"input x:";cin>>ptr->x;
cout<<"input y:";cin>>ptr->y;
//output POINT
cout<<"Output point:("<<ptr->x<<", "<<ptr->y<<")"<<endl;
// ptr memory is released
delete ptr;
ptr = NULL;
system("pause");
}
Kết quả
Input point
input x:5
input y:9
Output point:(5, 9)