Có hai kỹ thuật phân bổ bộ nhớ được sử dụng rộng rãi: phân bổ tự động và phân bổ động. Thông thường, có một vùng bộ nhớ tương ứng cho mỗi vùng: ngăn xếp và đống.
Cây rơm
Ngăn xếp luôn phân bổ bộ nhớ theo kiểu liên tiếp. Nó có thể làm như vậy bởi vì nó yêu cầu bạn giải phóng bộ nhớ theo thứ tự ngược lại (First-In, Last-Out: FILO). Đây là kỹ thuật cấp phát bộ nhớ cho các biến cục bộ trong nhiều ngôn ngữ lập trình. Nó rất, rất nhanh bởi vì nó đòi hỏi sổ sách tối thiểu và địa chỉ tiếp theo để phân bổ là ẩn.
Trong C ++, đây được gọi là lưu trữ tự động vì lưu trữ được yêu cầu tự động ở cuối phạm vi. Ngay khi thực hiện khối mã hiện tại (được phân định bằng cách sử dụng {}
), bộ nhớ cho tất cả các biến trong khối đó sẽ tự động được thu thập. Đây cũng là thời điểm mà hàm hủy được viện dẫn để các nguồn lực dọn dẹp.
Đống
Heap cho phép chế độ cấp phát bộ nhớ linh hoạt hơn. Sổ sách kế toán phức tạp hơn và phân bổ chậm hơn. Vì không có điểm phát hành ngầm, bạn phải giải phóng bộ nhớ theo cách thủ công, sử dụng delete
hoặc delete[]
( free
bằng C). Tuy nhiên, sự vắng mặt của một điểm phát hành ngầm là chìa khóa cho tính linh hoạt của heap.
Lý do nên sử dụng phân bổ động
Ngay cả khi việc sử dụng heap chậm hơn và có khả năng dẫn đến rò rỉ bộ nhớ hoặc phân mảnh bộ nhớ, vẫn có những trường hợp sử dụng hoàn toàn tốt để phân bổ động, vì nó ít bị hạn chế hơn.
Hai lý do chính để sử dụng phân bổ động:
Bạn không biết bạn cần bao nhiêu bộ nhớ trong thời gian biên dịch. Chẳng hạn, khi đọc tệp văn bản thành một chuỗi, bạn thường không biết tệp có kích thước như thế nào, vì vậy bạn không thể quyết định phân bổ bao nhiêu bộ nhớ cho đến khi bạn chạy chương trình.
Bạn muốn phân bổ bộ nhớ sẽ tồn tại sau khi rời khỏi khối hiện tại. Chẳng hạn, bạn có thể muốn viết một hàm string readfile(string path)
trả về nội dung của tệp. Trong trường hợp này, ngay cả khi ngăn xếp có thể chứa toàn bộ nội dung tệp, bạn không thể trả về từ một hàm và giữ khối bộ nhớ được phân bổ.
Tại sao phân bổ động thường không cần thiết
Trong C ++, có một cấu trúc gọn gàng được gọi là hàm hủy . Cơ chế này cho phép bạn quản lý tài nguyên bằng cách sắp xếp thời gian tồn tại của tài nguyên với thời gian tồn tại của một biến. Kỹ thuật này được gọi là RAII và là điểm phân biệt của C ++. Nó "bọc" tài nguyên vào các đối tượng. std::string
là một ví dụ hoàn hảo. Đoạn trích này:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
thực sự phân bổ một lượng bộ nhớ thay đổi. Đối std::string
tượng phân bổ bộ nhớ bằng cách sử dụng heap và giải phóng nó trong hàm hủy của nó. Trong trường hợp này, bạn không cần phải quản lý thủ công bất kỳ tài nguyên nào và vẫn nhận được lợi ích của việc cấp phát bộ nhớ động.
Cụ thể, nó ngụ ý rằng trong đoạn trích này:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
có sự phân bổ bộ nhớ động không cần thiết. Chương trình yêu cầu nhập nhiều hơn (!) Và đưa ra nguy cơ quên mất việc sắp xếp bộ nhớ. Nó làm điều này không có lợi ích rõ ràng.
Tại sao bạn nên sử dụng lưu trữ tự động càng thường xuyên càng tốt
Về cơ bản, đoạn cuối tổng hợp nó. Sử dụng lưu trữ tự động thường xuyên nhất có thể làm cho các chương trình của bạn:
- gõ nhanh hơn;
- nhanh hơn khi chạy;
- ít bị rò rỉ bộ nhớ / tài nguyên.
Điểm thưởng
Trong câu hỏi tham khảo, có những mối quan tâm bổ sung. Đặc biệt, lớp sau:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Thực sự có nhiều rủi ro hơn khi sử dụng so với cách sau:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
Lý do là std::string
xác định đúng một hàm tạo sao chép. Hãy xem xét chương trình sau:
int main ()
{
Line l1;
Line l2 = l1;
}
Sử dụng phiên bản gốc, chương trình này có thể sẽ gặp sự cố, vì nó sử dụng delete
trên cùng một chuỗi hai lần. Sử dụng phiên bản sửa đổi, mỗi Line
dụ sẽ sở hữu chuỗi riêng của mình Ví dụ , mỗi bộ nhớ riêng của mình và cả hai sẽ được phát hành vào cuối chương trình.
Ghi chú khác
Việc sử dụng rộng rãi RAII được coi là một cách thực hành tốt nhất trong C ++ vì tất cả các lý do trên. Tuy nhiên, có một lợi ích bổ sung không rõ ràng ngay lập tức. Về cơ bản, nó tốt hơn tổng số các bộ phận của nó. Toàn bộ cơ chế sáng tác . Nó vảy.
Nếu bạn sử dụng Line
lớp làm khối xây dựng:
class Table
{
Line borders[4];
};
Sau đó
int main ()
{
Table table;
}
phân bổ bốn std::string
trường hợp, bốn Line
trường hợp, một Table
thể hiện và tất cả nội dung của chuỗi và mọi thứ được giải phóng tự động .