Phụ lục cho bài viết về auto_ptr: Tìm hiểu thêm về lệnh delete
Nhân tiện bài viết trước về auto_ptr, chúng ta ngó qua một chút về lệnh delete thông qua một số Q&A.
Nguồn: http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.11
Q: Lệnh delete p xóa con trỏ p hay xóa vùng nhớ trỏ đến bởi p?
A: Vùng nhớ được trỏ đến bởi p.
Khi viết delete p có nghĩa là delete_the_thing_pointed_to_by p. Chúng ta có tình huống tương tự khi giải phóng bộ nhớ bởi lệnh free. free( p ) thực chất là free_the_stuff_pointed_to_by( p ).
Q: Có an toàn không nếu gọi delete hai lần cho cùng một con trỏ?
A: Không, nếu không có lệnh new nào cho con trỏ đó xen vào giữa.
Đoạn mã sau có thể gây ra thảm họa
class Foo { ... };
void yourCode()
{
Foo* p = new Foo();
delete p;
delete p; //← thảm họa!
...
}
Lệnh delete thứ hai có thể gây ra những thảm họa như làm hỏng vùng nhớ heap, làm đổ vỡ chương trình, làm thay đổi một cách tùy ý các object đang tồn tại trong heap. Thật không may, những hậu quả này xảy ra một cách khá ngẫu nhiên. Một số môi trường chạy (runtime environment) có thể giúp bạn tránh được những hậu quả của việc delete hai lần trong một số trường hợp đơn giản. Tuy nhiên, delete một con trỏ hai lần vẫn là một việc làm tồi tệ.
Q: Có cần kiểm tra con trỏ NULL trước khi delete hay không?
A: Không
C++ đảm bảo rằng lệnh delete p sẽ không làm gì cả nếu p là một con trỏ NULL. Bởi vậy, lệnh if trong đoạn mã sau đây là thừa
if( p != NULL )
delete p;
Q: Điều gì thật sự xảy ra khi viết delete p?
A: delete p thực hiện hai việc: Gọi destructor của đối tượng được trỏ tới bởi p và giải phóng vùng nhớ của đối tượng đó. delete p có chức năng tương tự như đoạn mã sau đây, với p là con trỏ kiểu Fred*
if( p!= NULL ) {
p->~Fred();
operator delete( p );
}
Lệnh p->~Fred() gọi destructor của đối tượng Fred được p trỏ tới. Lệnh operator delete( p ) gọi tới lệnh giải phóng bộ nhớ nguyên thủy void operator delete( void* p );
Q: Làm thế nào để cấp phát và giải phóng vùng nhớ cho một mảng các đối tượng
A: Sử dụng p = new T[ n ] và delete [ ] p;
Ví dụ, để cấp phát vùng nhớ cho một mảng chứa tối đa 100 đối tượng kiểu Fred, sau đó giải phóng vùng nhớ này, chúng ta dùng đoạn mã sau
Fred* p = new Fred[ 100 ]; ... delete [ ] p;
Q: Điều gì sẽ xảy ra nếu chúng ta quên viết [ ] khi giải phóng vùng nhớ cấp phát bởi new T[ n ] ?
A: Sẽ là một thảm họa.
Người lập trình, chứ không phải trình dịch, có trách nhiệm đảm bảo rằng một vùng nhớ đã được cấp phát bởi lệnh T* p = new T[ n ] luôn phải được giải phóng bởi lệnh delete [ ] p. Nếu không làm vậy, sẽ chẳng có dòng thông báo lỗi nào xảy ra lúc biên dịch hay chạy chương trình cả. Tuy nhiên, vùng nhớ heap có thể bị phá hỏng hoặc chương trình bị đổ vỡ.
Liên quan đến toán tử new và delete thì cũng cần tham khảo thêm các item trong phần memory management của Effective C++ – Scott Mayers
Item 5: Sử dụng cùng một form đối với toán tử new và delete
Item 6: Dùng delete trong hàm destructor nếu có pointer members.
Item 7: Cần chuẩn bị sẵn sàng khi out of memory
Item 8: Tuân theo convention khi viết toán tử new và delete
Item 9: Tránh việc ẩn giấu đi dạng “thông thường” của toán tử new
Item 10: Hãy viết toán tử delete khi bạn viết toán tử new.