Lập trình

Bộ nhớ động trong C/C++: Toán tử new và delete

Huy Erick

Hiểu sâu về cách bộ nhớ động thực sự hoạt động trong C/C++ là một yếu tố quan trọng để trở thành một lập trình viên C/C++ giỏi. Trong chương trình C/C++ của bạn, bộ...

Hiểu sâu về cách bộ nhớ động thực sự hoạt động trong C/C++ là một yếu tố quan trọng để trở thành một lập trình viên C/C++ giỏi. Trong chương trình C/C++ của bạn, bộ nhớ được phân thành hai phần: Stack và Heap.

Stack và Heap

  • Stack: Các biến được khai báo bên trong hàm sẽ nhận bộ nhớ từ stack trong C/C++.
  • Heap: Được sử dụng để cấp phát bộ nhớ động khi chương trình chạy.

Trong một số trường hợp, bạn không biết trước cần bao nhiêu bộ nhớ để lưu thông tin trong một biến đã được định nghĩa và kích cỡ bộ nhớ cần thiết có thể được quyết định tại thời gian chạy.

Bạn có thể cấp phát bộ nhớ tại thời gian chạy trong Heap cho biến đó bằng cách sử dụng toán tử đặc biệt trong C/C++ trả về địa chỉ của không gian đã cấp phát. Toán tử này được gọi là toán tử new trong C/C++.

Để giải phóng bộ nhớ đã được cấp phát trước đó bởi toán tử new, bạn có thể sử dụng toán tử delete trong C/C++.

Toán tử new và delete trong C/C++

Dưới đây là cú pháp chung để sử dụng toán tử new để cấp phát bộ nhớ động cho bất kỳ kiểu dữ liệu nào trong C/C++:

``` new kieu_du_lieu; ``` Ở đây, `kieu_du_lieu` có thể là bất kỳ kiểu dữ liệu có sẵn nào, ví dụ như mảng hoặc các kiểu dữ liệu tự định nghĩa như lớp hoặc cấu trúc. Đầu tiên, chúng ta sẽ xem xét các kiểu dữ liệu có sẵn.

Ví dụ, chúng ta có thể định nghĩa một con trỏ tới kiểu double và sau đó yêu cầu cấp phát bộ nhớ tại thời gian chạy. Điều này có thể được thực hiện bằng cách sử dụng toán tử new trong C/C++ như sau:

double* conTro = NULL; // con trỏ được khởi tạo với giá trị null
conTro = new double; // yêu cầu bộ nhớ cho biến

Trong trường hợp bộ nhớ chưa được cấp phát thành công vì phần bộ nhớ rỗi đã được sử dụng, bạn nên kiểm tra xem toán tử `new` có trả về con trỏ NULL hay không và thực hiện hành động phù hợp.

``` double* conTro = NULL; if (!(conTro = new double)) { cout "Lỗi: Hết bộ nhớ!" endl; exit(1); } ```

Hàm `malloc()` từ C vẫn tồn tại trong C/C++, tuy nhiên, tôi đề nghị tránh sử dụng hàm `malloc()`. Lợi thế lớn nhất của toán tử `new` so với hàm `malloc()` là toán tử `new` không chỉ cấp phát bộ nhớ, mà còn xây dựng đối tượng theo mục đích chính của C/C++.

Để giải phóng bộ nhớ mà một biến đã cấp phát động đang chiếm giữ trong phần bộ nhớ rỗi, bạn có thể sử dụng toán tử `delete` trong C/C++, như sau:

``` delete conTro; // giải phóng bộ nhớ được trỏ bởi conTro ```

Dựa vào các khái niệm trên, chúng ta sẽ tạo một ví dụ minh họa về cách toán tử new và delete trong C/C++ hoạt động:

```cpp #include using namespace std;

int main() { double conTro = NULL; // con trỏ được khởi tạo với giá trị null conTro = new double; // yêu cầu bộ nhớ cho biến conTro = 1234.56; // lưu giữ giá trị tại địa chỉ đã cấp phát cout << "Giá trị của conTro là: " << *conTro << endl; delete conTro; // giải phóng bộ nhớ return 0; }

<p>Sau khi biên dịch và chạy chương trình C/C++, kết quả sẽ là:</p>

Giá trị của conTro là: 1234.56


## Cấp phát bộ nhớ động cho mảng trong C/C++
<p>Giả sử bạn muốn cấp phát bộ nhớ cho một mảng ký tự, ví dụ như một chuỗi 20 ký tự. Bạn sử dụng cùng cú pháp đã sử dụng ở trên, như sau:</p>
```cpp
char* conTro = NULL; // con trỏ được khởi tạo với giá trị null
conTro = new char[20]; // yêu cầu bộ nhớ cho biến

Để xóa mảng mà chúng ta vừa tạo, cú pháp trong C/C++ là:

``` delete [] conTro; // xóa mảng đã được trỏ bởi conTro ```

Cú pháp để cấp phát bộ nhớ cho một mảng đa chiều trong C/C++ cũng tương tự:

```cpp double** conTro = NULL; // con trỏ được khởi tạo với giá trị null conTro = new double [3][4]; // cấp phát bộ nhớ cho mảng 3x4 ```

Tuy nhiên, cú pháp để giải phóng bộ nhớ cho mảng đa chiều vẫn giống như trên:

``` delete [] conTro; // xóa mảng đã được trỏ bởi conTro ```

Cấp phát bộ nhớ động cho đối tượng trong C/C++

Đối tượng không khác các kiểu dữ liệu đơn giản. Ví dụ, bạn có thể xem xét đoạn code sau, trong đó chúng ta sử dụng một mảng đối tượng để làm rõ khái niệm này:

```cpp #include using namespace std;

class NhanVien { public: NhanVien() { cout << "Constructor được gọi!" << endl; } ~NhanVien() { cout << "Destructor được gọi!" << endl; } };

int main() { NhanVien* mangNhanVien = new NhanVien[5]; delete [] mangNhanVien; // xóa mảng return 0; }

<p>Nếu bạn chuẩn bị cấp phát bộ nhớ cho một mảng 5 đối tượng NhanVien, constructor đơn giản trên sẽ được gọi 4 lần và tương tự khi xóa các đối tượng, destructor cũng được gọi với số lần tương tự.</p>

<p>Sau khi biên dịch và chạy chương trình C/C++, kết quả sẽ là:</p>

Constructor được gọi! Constructor được gọi! Constructor được gọi! Constructor được gọi! Destructor được gọi! Destructor được gọi! Destructor được gọi! Destructor được gọi! Destructor được gọi!



![Bộ nhớ động trong C/C++](https://vietjack.com/../cplusplus/images/bo_nho_dong_trong_cplusplus.PNG)
1