2008-10-31 43 views
23

Đây là một câu hỏi đã khiến tôi khó chịu trong một thời gian. Tôi luôn luôn nghĩ rằng C++ nên được thiết kế sao cho toán tử "delete" (không có dấu ngoặc) hoạt động ngay cả với toán tử "new []".Tại sao chúng ta cần toán tử "delete []"?

Theo tôi, viết này:

int* p = new int; 

nên tương đương với phân bổ một mảng của 1 phần tử:

int* p = new int[1]; 

Nếu đây là sự thật, "delete" nhà điều hành có thể luôn luôn được xóa mảng và chúng tôi sẽ không cần toán tử "delete []".

Có lý do nào khiến toán tử "delete []" được giới thiệu trong C++ không? Lý do duy nhất tôi có thể nghĩ là phân bổ mảng có một bộ nhớ nhỏ (bạn phải lưu trữ kích thước mảng ở đâu đó), để phân biệt "xóa" và "xóa []" là một tối ưu hóa bộ nhớ nhỏ.

Trả lời

32

Đó là để các trình phá hủy của các thành phần riêng lẻ sẽ được gọi. Có, đối với mảng POD, không có nhiều khác biệt, nhưng trong C++, bạn có thể có mảng đối tượng với các trình phá hủy không tầm thường.

Bây giờ, câu hỏi của bạn, tại sao không làm cho newdelete cư xử như new[]delete[] và thoát khỏi new[]delete[]? Tôi sẽ quay trở lại cuốn sách "Thiết kế và tiến hóa" của Stroustrup, nơi ông nói rằng nếu bạn không sử dụng các tính năng của C++, bạn không cần phải trả tiền cho chúng (ít nhất là trong thời gian chạy). Cách hiện tại, new hoặc delete sẽ hoạt động hiệu quả như mallocfree. Nếu delete có ý nghĩa delete[], sẽ có thêm một số chi phí bổ sung khi chạy (như James Curran đã chỉ ra).

+4

Thực tế, khi bạn sử dụng int mới [1] nó lưu trữ trên đầu mảng trước khi dữ liệu đầu tiên, kích thước của nó. Vì vậy, việc sử dụng xóa thay vì xóa [] sẽ không giải phóng phần bộ nhớ đó. – Rodrigo

2

delete [] đảm bảo rằng hủy của mỗi thành viên được gọi (nếu áp dụng cho loại) trong khi delete chỉ xóa bộ nhớ được cấp phát cho mảng đó.

Dưới đây là một đọc tốt: http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287

Và không, kích thước mảng không được lưu trữ bất cứ nơi nào trong C++. (Cảm ơn tất cả mọi người đã chỉ ra rằng tuyên bố này là không chính xác.)

+0

Không đồng ý với tuyên bố cuối cùng của bạn. Trình biên dịch phải biết kích thước mảng để gọi hàm hủy cho mỗi đối tượng trong mảng. Tôi nghĩ rằng bạn đang bối rối điều này với thực tế là C + + không làm kiểm tra giới hạn trên mảng. –

+0

Ồ đúng. Tôi nghĩ bạn đã gợi ý rằng kích thước sẽ được lưu trữ như là một phần của cấu trúc dữ liệu mảng (bộ đệm). Có, trình biên dịch có thể sẽ lưu trữ thông tin kích thước ở đâu đó ... –

+0

Một cách tiếp cận là lưu trữ kích thước và số phần tử trong từ trước khi bắt đầu mảng. Đây được gọi là một cookie. – Dynite

3

Marshall Cline có một số info on this topic.

+0

Liên kết đó không giải quyết tại sao ngôn ngữ được thiết kế để cần các toán tử 'delete' và' delete [] 'riêng biệt. – jamesdlin

6

Vì mọi người khác dường như đã bỏ lỡ điểm của câu hỏi của bạn, tôi sẽ chỉ thêm rằng tôi đã có suy nghĩ tương tự cách đây một năm và chưa bao giờ có thể nhận được câu trả lời.

Điều duy nhất tôi có thể nghĩ đến là có một chút rất nhỏ của chi phí thêm để điều trị một đối tượng duy nhất như là một mảng (một không cần thiết "for(int i=0; i<1; ++i)")

+1

Thêm một chút bộ nhớ để lưu trữ kích thước. –

+0

Vâng, tôi đặt cược rằng chi phí bộ nhớ được coi là không thể chấp nhận. Có thể vòng lặp cũng vậy. –

8

Chết tiệt, tôi đã bỏ lỡ toàn bộ vấn đề của câu hỏi nhưng Tôi sẽ để lại câu trả lời ban đầu của tôi như là một sidenote. Tại sao chúng ta xóa [] là bởi vì thời gian dài trước đây chúng ta đã xóa [cnt], ngay cả ngày hôm nay nếu bạn viết delete [9] hoặc delete [cnt], trình biên dịch chỉ bỏ qua điều giữa [] nhưng biên dịch ok. Vào thời điểm đó, C++ lần đầu tiên được xử lý bởi một front-end và sau đó được nạp vào một trình biên dịch C thông thường. Họ không thể làm được việc lưu trữ số lượng ở đâu đó bên dưới bức màn, có lẽ họ thậm chí không thể nghĩ về nó vào lúc đó.Và đối với tính tương thích ngược, các trình biên dịch có thể sử dụng giá trị nhất định giữa [] làm số lượng mảng, nếu không có giá trị như vậy thì chúng có số đếm từ tiền tố, vì vậy nó làm việc theo cả hai cách. Sau đó, chúng tôi đã gõ không có gì giữa [] và mọi thứ đã hoạt động. Hôm nay, tôi không nghĩ rằng "xóa []" là cần thiết nhưng việc triển khai yêu cầu nó theo cách đó.

Câu trả lời gốc của tôi (bỏ lỡ điểm) ::

"xóa" sẽ xóa một đối tượng. "delete []" xóa một mảng đối tượng. Để xóa [] để làm việc, việc thực hiện giữ số phần tử trong mảng. Tôi chỉ cần kiểm tra lại điều này bằng cách gỡ lỗi mã ASM. Trong việc thực hiện (VS2005) tôi đã thử nghiệm, số lượng được lưu trữ như là một tiền tố cho mảng đối tượng.

Nếu bạn sử dụng "xóa []" trên một đối tượng duy nhất, biến số đếm là rác để mã bị treo. Nếu bạn sử dụng "xóa" cho một mảng đối tượng, vì một số mâu thuẫn, mã bị treo. Tôi đã thử nghiệm những trường hợp này ngay bây giờ!

"xóa chỉ xóa bộ nhớ được cấp cho mảng". tuyên bố trong câu trả lời khác là không đúng. Nếu đối tượng là một lớp, xóa sẽ gọi DTOR. Chỉ cần đặt một breakpoint vào mã DTOR và xóa đối tượng, điểm ngắt sẽ nhấn. Điều gì xảy ra với tôi là, nếu biên dịch & thư viện giả định rằng tất cả các đối tượng được phân bổ bởi "mới" là mảng đối tượng, nó sẽ là OK để gọi "xóa" cho các đối tượng duy nhất hoặc mảng đối tượng. đối tượng duy nhất chỉ sẽ là trường hợp đặc biệt của một mảng đối tượng có một đếm 1. Có lẽ có cái gì đó tôi đang thiếu, dù sao ...

5

Thêm này vì không có câu trả lời khác hiện đang đề cập đến nó:

Mảng delete[] không thể được sử dụng trên lớp con trỏ đến cơ sở - trong khi trình biên dịch lưu trữ số lượng đối tượng khi bạn gọi new[], nó không lưu trữ các loại hoặc kích cỡ của đối tượng (như David đã chỉ ra, trong C++ bạn hiếm khi trả tiền cho một tính năng bạn không sử dụng). Tuy nhiên, vô hướng delete có thể xóa một cách an toàn thông qua lớp cơ sở, vì vậy nó được sử dụng cho cả dọn dẹp đối tượng bình thường và dọn dẹp đa hình:

struct Base { virtual ~Base(); }; 
struct Derived : Base { }; 
int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    Base* b = new Derived[2]; 
    delete[] b; // bad! undefined behavior 
} 

Tuy nhiên, trong trường hợp ngược lại - không ảo destructor - vô hướng delete nên càng rẻ càng tốt - nó không nên kiểm tra số lượng đối tượng, cũng như đối với loại đối tượng bị xóa. Điều này làm cho xóa trên một loại built-in hoặc đồng bằng tuổi-kiểu dữ liệu rất rẻ, như trình biên dịch chỉ cần gọi ::operator delete và không có gì khác:

int main(){ 
    int * p = new int; 
    delete p; // cheap operation, no dynamic dispatch, no conditional branching 
} 

Trong khi không phải là một điều trị đầy đủ các cấp phát bộ nhớ, tôi hy vọng điều này sẽ giúp làm rõ chiều rộng của các tùy chọn quản lý bộ nhớ có sẵn trong C++.

0

Tôi hơi bối rối bởi câu trả lời của Aaron và thẳng thắn thừa nhận tôi không hoàn toàn hiểu tại sao và nơi xóa [] là cần thiết.

Tôi đã thực hiện một số thử nghiệm với mã mẫu của anh ấy (sau khi sửa một vài lỗi chính tả). Đây là kết quả của tôi. Typos: ~ cơ sở cần một chức năng cơ thể Base * b được khai báo hai lần

struct Base { virtual ~Base(){ }>; }; 
struct Derived : Base { }; 
int main(){ 
Base* b = new Derived; 
delete b; // this is good 

<strike>Base</strike> b = new Derived[2]; 
delete[] b; // bad! undefined behavior 
} 

Compilation và thực hiện

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
[email protected]: # No error message 

Modified chương trình với delete [] loại bỏ

struct Base { virtual ~Base(){}; }; 
struct Derived : Base { }; 

int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    b = new Derived[2]; 
    delete b; // bad! undefined behavior 
} 

Compilation và thực hiện

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
atest(30746) malloc: *** error for object 0x1099008c8: pointer being freed was n 
ot allocated 
*** set a breakpoint in malloc_error_break to debug 
Abort trap: 6 

Tất nhiên, tôi không biết liệu xóa [] b có thực sự hoạt động trong ví dụ đầu tiên hay không; Tôi chỉ biết nó không đưa ra thông báo lỗi biên dịch.

Các vấn đề liên quan