[C++ cơ bản] Cấp phát bộ nhớ động với C++ (phần 2)

cppĐể chuẩn bị cho loạt bài “Hướng đối tượng ứng dụng thiết kế cấu trúc cây”, tôi viết trước bài này để các bạn có cái nhìn tổng quan và rõ hơn về việc cấp phát bộ nhớ động để ứng dụng thiế kế cấu trúc cây. Bài viết phần hai này sẽ nhắc lại và mở rộng thêm về khái niệm sử dụng ô nhớ trên Heap.

Nếu bạn chưa có cái nhìn tổng quan nhất về cấp phát bộ nhớ động, bạn có thẻ đọc lại phần 1 của bài này tại: https://nvconghau.wordpress.com/2014/08/14/cc-co-ban-cap-phat-bo-nho-dong-voi-c/ . Sau đó, bạn có thể tiếp tục tại đây.

Nhắc lại & mở rộng việc sử dụng bộ nhớ trên vùng lưu trữ tự do (Heap)

Vùng lưu trữ tự do, heap, là một vùng nhớ chưa được khai thác trên bộ nhớ máy tính. Các biến toàn cục và cục bộ của một chương trình phải được “đăng ký” địa chỉ trước trên bộ nhớ bằng các câu lệnh khai báo tạo biến, ví dụ như: “int bienA;”. Tuy nhiên, khác với vùng nhớ để bạn lưu trữ các biến toàn cục cũng như cục bộ của một chương trình, các ô nhớ trên vùng lưu trữ tự do không cần phải được đăng ký địa chỉ trước. Tức là, có rất nhiều ô nhớ được cấp cho bạn ngay trong lúc chương trình đang chạy.

Bạn có thể coi việc sử dụng các biến toàn cục, cục bộ như việc bạn phải đăng ký với nhà nước (ở đây có thể xem như hề điều hành) mảnh đất mình được sử dụng, nhà nước đồng ý cấp cho bạn mảnh đất đó với một chi phí nhất định, và bạn không thể thay đổi chi phí đó.

Ví dụ mảnh đất bạn rộng 400 trăm mét vuông, hay một biến int chiếm 4 byte trên RAM, kể từ lúc bắt đầu khối lệnh chứa khai báo biến đó đến khi kết thúc khối lệnh, bạn sẽ không thể thay đổi hay mở rộng mảnh đất của mình. Bạn chỉ có thể thay đổi tài sản được đặt trên mảnh đất đó, ví dụ nhà cửa, cây trái trên mảnh đất (ở đây là giá trị của biến). Khi khối lệnh chứa khai báo biến của bạn kết thúc, biến của bạn sẽ bị hủy (unset) và qua câu lệnh sau khối đó, biến đã không còn tồn tại (với bạn). Điều này cũng có thể xem như bạn xin đầu tư từ nhà nước lên một mảnh đất 400 trăm mét vuông và chỉ được cấp phép sử dụng mảnh đất đó trong 4 năm (4 dòng lệnh của khối mã chẳng hạn). Ví dụ dưới đây minh họa rõ ràng vấn đề này.

#include <iostream>
using namespace std;

void suDungDat() {
    int manhDat = 0; // Nam dau tien, bat dau nhan dat, rao dat... 
    manhDat += 1; // nam thu hai, tu mot manh dat trong khong, ban cho no them mot can nha. 
     manhDat += 1; // nam thu ba, ban cho no them mot cai ho boi. 
     manhDat += 1; // nam thu tu, ban xay them gara xe hoi. 
      cout << "Tong so tai san hien gio tren dat la: " << manhDat << endl; // nam thu 4, ban bat dau khoe ve manh dat cua minh 
}
int main() {
   cout << "Toi muon xin Nha nuoc cho dau tu mot manh dat 400 tram met vuong trong vong 4 nam duoc khong?" << endl;
   cout << "NHA NUOC: duoc, anh se duoc cap phep dau tu manh dat 400 tram met vuong do trong vong 4 nam." << endl;
     suDungDat();
 // het 4 nam, ban khong the su dung manh dat nua!!!
 cout << "Hey, day la manh dat cua tui ne! " << manhDat << endl;
  cout << "NHA NUOC: nay anh kia, anh da het 4 nam duoc cap phep nen khong duoc su dung dat nua!" << endl;
 return 0;
}

Bạn hãy thử biên dịch đoạn mã trên và sẽ nhận được lỗi đại loại như: [error] ‘manhDat’ was not declared in this crope ở dòng:

cout << "Hey, day la manh dat cua tui ne! " << manhDat << endl;

Để chương trình có thể chạy, bạn hãy thêm vào dấu comment đằng trước câu lệnh trên.

// cout << "Hey, day la manh dat cua tui ne! " << manhDat << endl;

Sau khi chạy chương trình, bạn sẽ nhận được kết quả sau:

Toi muon xin Nha nuoc cho dau tu mot manh dat 400 tram met vuong trong vong 4 nam duoc khong?
NHA NUOC: duoc, anh se duoc cap phep dau tu manh dat 400 tram met vuong do trong vong 4 nam.
Tong so tai san hien gio tren dat la: 3
NHA NUOC: nay anh kia, anh da het 4 nam duoc cap phep nen khong duoc su dung dat nua!

Bởi vì sau khi kết thúc hàm suDungDat(), mọi biến cục bộ được tạo bên trong nội tại của nó đề bị hủy nên biến manhDat không còn hiệu lực trong hàm main() nữa!

Bạn có thể xin cấp phép sử dụng đất dài hạn, tức là chỉ khi nào bạn trả lại đất thì mới không dùng được nữa (khi dùng từ khóa delete hoặc kết thúc chương trình), bằng cách tạo ra các con trỏ đến ô nhớ trên Heap.

Thử sửa lại chương trình như sau:

#include <iostream>
using namespace std;
int suDungDat() {
     int * manhDat = new int(0); // Nam dau tien, bat dau nhan dat, rao dat... 
     manhDat += 1; // nam thu hai, tu mot manh dat trong khong, ban cho no them mot can nha. 
     manhDat += 1; // nam thu ba, ban cho no them mot cai ho boi.
     manhDat += 1; // nam thu tu, ban xay them gara xe hoi. 
      cout << "Tong so tai san hien gio tren dat la: " << manhDat << endl; // nam thu 4, ban bat dau khoe ve manh dat cua minh 
}
 int main() { 
     cout << "Toi muon xin Nha nuoc cho dau tu mot manh dat 400 tram met vuong dai han duoc khong?" << endl; 
      cout << "NHA NUOC: duoc, anh se duoc cap phep dau tu manh dat 400 tram met vuong do dai han." << endl; 
       int * manhDat = suDungDat();  

       cout << "Hey, day la manh dat cua tui ne! " << *manhDat << endl; 
       cout << endl<< "Chan dau tu roi, toi tral ai dat day..." << endl; 
        delete manhDat;  
        cout << "Sau khi tra lai dat, no khong con la cua toi nua: " << *manhDat << endl; 
        return 0; 
}

Lần này, hàm suDungDat() đã được khai báo trả về là một con trỏ kiểu int. Trong hàm, con trỏ manhDat kiểu int được khởi tạo và khai báo trỏ về một ô nhớ trên vùng lưu trữ tự dó có giá trì là 0 (kiểu int) bằng câu lệnh: “int * manhDat = new int(0)”.

Sau khi kết thức hàm suDungDat(), nó trả về giá trị là địa chỉ của ô nhớ mà con trỏ manhDat trỏ đến.

Vì giá trị của con tro manhDat trỏ đến được khai báo trên vùng lưu trữ tự do nên dù đã ra khỏi hàm suDungDat() nhưng giá trị của ô nhớ đó vẫn không thay đổi.

Khi ta chán miếng đất đó rồi thì ta có thể hủy việc sử dụng nó bằng lệnh “delete manhDat;” .

Đây là kết quả suất:

Toi muon xin Nha nuoc cho dau tu mot manh dat 400 tram met vuong dai han duoc k
hong?
NHA NUOC: duoc, anh se duoc cap phep dau tu manh dat 400 tram met vuong do dai h
an.
Tong so tai san hien gio tren dat la: 3
Hey, day la manh dat cua tui ne! 3
Chan dau tu roi, toi tral ai dat day...
Sau khi tra lai dat, no khong con la cua toi nua: 4132312

Rõ ràng là việc sử dụng đất dài hạn đã được minh chứng sau khi kết thúc các lệnh trong khối mã con, giá trị của ô nhở con trỏ manhDat  trỏ đến vẫn giữa nguyên. Sau khi hủy ô nhớ được con trỏ manhDat tro toi, gia tri của nó đã bị hủy.

Tất nhiên, trên đây chỉ là một ví dụ đơn giản về cấp bộ nhớ động. Vấn đề của chương trình trên có thể được giải quyết bằng biến toàn cục. Tuy nhiên, hãy tưởng tượng bạn có thể tạo ra một ô nhớ trên Heap ở một hàm, và trả về địa của nó, hàm gọi nó có thể tiếp nhận địa chỉ này và thao tác nó một cách an toàn vì biết dữ liệu từ đâu đến và sẽ làm gì tiếp theo. Còn khi sử dụng biến toàn cục, bất cứ nơi nào của chương trình cũng có thể thay đổi giá trị của nó. Điều này thường đường các nhà lập trình cảnh báo là vô cùng nguy hiểm.

Ở bài viết tiếp theo, ta sẽ nghiên cứu cách thức tạo các ô nhớ để lưu trữ các đối tượng trên vùng lưu trữ tự do. Đây là ứng dụng rất quan trọng khi bạn cần tạo các “cấu trúc cây” bằng lập trình hướng đối tượng.

Công Hậu, nvconghau1995@gmail.com

2 thoughts on “[C++ cơ bản] Cấp phát bộ nhớ động với C++ (phần 2)”

Gửi bình luận