14/08/2018, 13:20

Bộ nhớ động trong C/C++

Một sự hiểu biết sâu về cách bộ nhớ động thực sự làm việc trong C/C++ là cốt yếu để trở thành một lập trình viên C/C++ giỏi. Bộ nhớ trong chương trình C/C++ của bạn được phân thành hai phần: Nhiều khi, bạn không biết trước bao nhiêu bộ nhớ bạn sẽ cần để lưu thông tin cụ thể trong một biến ...

Một sự hiểu biết sâu về cách bộ nhớ động thực sự làm việc trong C/C++ là cốt yếu để trở thành một lập trình viên C/C++ giỏi. Bộ nhớ trong chương trình C/C++ của bạn được phân thành hai phần:

Nhiều khi, bạn không biết trước bao nhiêu bộ nhớ bạn sẽ cần để lưu thông tin cụ thể 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 run time.

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

Nếu bạn không cần thiết bộ nhớ động đã cấp phát nữa, bạn có thể sử dụng toán tử delete trong C/C++, sẽ giải phóng bộ nhớ đã được cấp phát trước đó bởi toán tử new.

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

Đâ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 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 rằng bộ nhớ được cấp phát tại thời gian thực thi. Chúng ta có thể làm điều này bởi sử dụng toán tử new trong C/C++ với các lệnh sau:

double* contro  = NULL; // con tro duoc khoi tao voi gia tri null
contro  = new double;   // yeu cau bo nho cho bien

Bộ nhớ có thể chưa được cấp phát thành công, nếu phần bộ nhớ rỗi (free store) đã được sử dụng. Vì thế, đây là bài thực hành tốt cho bạn khi kiểm tra nếu toán tử new trả về con trỏ NULL và thực hiện hành động thích hợp, như sau:

double* contro  = NULL;
if( !(contro  = new double ))
{
   cout << "Error: Het bo nho!" <<endl;
   exit(1);

}

Hàm malloc() từ C, vẫn tồn tại trong C/C++, nhưng tôi đề nghị bạn tránh sử dụng hàm malloc() này. 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ớ, nó còn xây dựng đối tượng theo mục đích chính của C/C++.

Tại bất kỳ thời điểm, khi bạn cảm thấy một biến đã được cấp phát động là không cần thiết nữa, bạn có thể giải phóng bộ nhớ mà nó đã chiếm giữ trong phần bộ nhớ rỗi với toán tử delete trong C/C++, như sau:

delete contro;        // giai phong bo nho duoc tro boi contro

Bây giờ, dựa vào các khái niệm trên chúng tôi tạo một ví dụ minh họa cách toán tử new và delete trong C/C++ làm việc:

#include <iostream>
using namespace std;

int main ()
{
   double* contro  = NULL; // con tro duoc khoi tao voi gia tri null
   contro  = new double;   // yeu cau bo nho cho bien
 
   *contro = 1234.56;     // luu giu gia tri tai dia chi da duoc cap phat
   cout << "Gia tri cua contro la: " << *contro << endl;

   delete contro;         // giai phong bo nho.

   return 0;
}

Biên dịch và chạy chương trình C/C++ trên sẽ cho kết quả sau:

Gia tri cua contro la: 1234.56

Cấp phát bộ nhớ động cho Mảng trong C/C++

Giả sử bạn muốn cấp phát bộ nhớ cho một mảng ký tự, ví dụ một chuỗi 20 ký tự. Bạn sử dụng cùng cú pháp đã sử dụng ở trên, như sau:

char* contro  = NULL;   // con tro duoc khoi tao voi gia tri null
contro  = new char[20]; // yeu cau bo nho cho bien

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

delete [] contro;        // xoa mang da duoc tro boi contro

Đây là cú pháp chung của toán tử new, bạn có thể sử dụng để cấp phát bộ nhớ cho một mảng đa chiều trong C/C++:

double** contro  = NULL;     // con tro duoc khoi tao voi gia tri null
contro  = new double [3][4]; // cap phat bo nho cho mang 3x4

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

delete [] contro;        // xoa mang da duoc tro boi 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 xem xét code sau, trong đó chúng ta đang sử dụng một mảng đối tượng để làm rõ khái niệm này:

#include <iostream>
using namespace std;

class NhanVien
{
   public:
      NhanVien() { 
         cout << "Constructor duoc goi!" <<endl; 
         
      }
      ~NhanVien() { 
         cout << "Destructor duoc goi!" <<endl; 
         
      }
};

int main( )
{
   NhanVien* mangNhanVien = new NhanVien[5];

   delete [] mangNhanVien; // xoa mang

   return 0;
}

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ự trong khi xóa các đối tượng, destructor cũng sẽ được gọi với số lần tương tự.

Biên dịch và chạy chương trình C/C++ trên sẽ cho kết quả sau:

Bộ nhớ động trong C/C++

0