2009-12-16 28 views
9

Tôi biết rằng khi delete [] sẽ gây ra sự phá hủy cho tất cả các phần tử mảng và sau đó giải phóng bộ nhớ.Tại sao [] được sử dụng trong xóa (xóa []) thành mảng được cấp động tự do?

Tôi ban đầu nghĩ rằng trình biên dịch muốn nó chỉ để gọi destructor cho tất cả các yếu tố trong mảng, nhưng tôi cũng có một bộ đếm - Lập luận cho rằng đó là:

Heap bộ nhớ cấp phát phải biết kích thước của byte được phân bổ và sử dụng sizeof(Type) của nó có thể tìm thấy không có các yếu tố và để gọi thích hợp không có destructors cho một mảng để ngăn chặn rò rỉ tài nguyên.

Giả sử của tôi là chính xác hay không và hãy làm rõ sự nghi ngờ của tôi về điều đó.

Vì vậy, tôi không nhận được việc sử dụng [] trong delete []?

Trả lời

34

Scott Meyers nói trong cuốn sách C++ hiệu quả của mình: Mục 5: Sử dụng cùng một biểu mẫu trong cách sử dụng tương ứng của mới và xóa.

Câu hỏi lớn về xóa là: có bao nhiêu đối tượng nằm trong bộ nhớ bị xóa? Câu trả lời cho rằng xác định có bao nhiêu destructors phải được gọi.

Con trỏ có bị xóa điểm tới một đối tượng đơn lẻ hay một mảng đối tượng không? Cách duy nhất để xóa để biết là để bạn nói với nó. Nếu bạn không sử dụng dấu ngoặc trong việc sử dụng của bạn xóa, xóa giả định một đối tượng duy nhất được trỏ đến.

Ngoài ra, các cấp phát bộ nhớ có thể phân bổ nhiều không gian cần thiết để lưu trữ đối tượng của bạn và trong trường hợp này chia kích thước của khối nhớ được trả về bởi kích thước của mỗi đối tượng sẽ không hoạt động.

Tùy thuộc vào nền tảng, các chức năng _msize (windows), malloc_usable_size (linux) hoặc malloc_size (osx) sẽ cho bạn biết độ dài thực của khối được phân bổ. Thông tin này có thể được khai thác khi thiết kế các thùng chứa đang phát triển.

Một lý do khác khiến nó không hoạt động là Foo* foo = new Foo[10] gọi operator new[] để cấp phát bộ nhớ. Sau đó, delete [] foo; gọi operator delete[] để deallocate bộ nhớ. Vì các toán tử đó có thể bị quá tải, bạn phải tuân theo quy ước delete foo; gọi operator delete có thể có triển khai không tương thích với operator delete []. Đó là vấn đề ngữ nghĩa, không chỉ theo dõi số lượng đối tượng được cấp phát để phát hành đúng số lượng cuộc gọi hủy.

Xem thêm:

[16.14] After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?

Câu trả lời ngắn: Magic.

Câu trả lời dài: Hệ thống thời gian chạy lưu trữ số lượng đối tượng, n, nơi nào đó có thể truy xuất được nếu bạn chỉ biết con trỏ, p. Có hai kỹ thuật phổ biến làm điều này. Cả hai kỹ thuật này đang được sử dụng bởi các trình biên dịch cấp thương mại, cả hai đều có sự cân bằng, và cả hai đều không hoàn hảo.Những kỹ thuật này là:


EDIT: sau khi có ý kiến ​​@AndreyT đọc, tôi đào vào bản sao của tôi của Stroustrup "Thiết kế và Sự phát triển của C++" và trích như sau:

Làm thế nào để chúng tôi đảm bảo rằng một mảng được xóa một cách chính xác? Đặc biệt, làm thế nào để đảm bảo rằng destructor được gọi cho tất cả các phần tử của một mảng?

...

Xóa đồng bộ là không bắt buộc để xử lý cả hai đối tượng riêng lẻ một mảng. Điều này tránh làm phức tạp các trường hợp phổ biến của phân bổ và deallocating các đối tượng cá nhân. Nó cũng tránh encumbering các đối tượng cá nhân với các thông tin cần thiết cho mảng deallocation.

Phiên bản trung gian của lệnh xóa [] yêu cầu người lập trình chỉ định số phần tử của mảng.

...

Điều đó tỏ ra quá dễ bị lỗi, do đó, gánh nặng theo dõi số lượng phần tử được đặt vào thực hiện thay thế.

Như @Marcus đã đề cập, lý trí có thể là "bạn không trả tiền cho những gì bạn không sử dụng".


EDIT2:

Trong "The C++ Ngôn ngữ lập trình, 3rd edition", §10.4.7, Bjarne Stroustrup viết:

Chính xác như thế nào mảng và các đối tượng cá nhân được Nhà nước giao là implementation- phụ thuộc. Do đó, các triển khai khác nhau sẽ phản ứng khác nhau đối với việc sử dụng không chính xác của các toán tử xóa và xóa []. Trong các trường hợp đơn giản và không quan tâm như trường hợp trước, trình biên dịch có thể phát hiện vấn đề, nhưng nói chung điều gì đó khó chịu sẽ xảy ra vào thời gian chạy.

Toán tử hủy diệt đặc biệt cho mảng, xóa [], không cần thiết về mặt logic. Tuy nhiên, giả sử việc thực hiện kho lưu trữ miễn phí đã được yêu cầu phải chứa đủ thông tin cho mọi đối tượng để cho biết đó là một cá nhân hay một mảng. Người dùng có thể đã được giảm bớt gánh nặng, nhưng nghĩa vụ đó sẽ áp đặt các chi phí đáng kể về thời gian và không gian trên một số triển khai C++.

+0

+1 cho sách của Scott Meyers: các lập trình viên _all_ C++ nên sở hữu và đọc sách C++ hiệu quả và hiệu quả hơn. – AAT

+2

Lời giải thích * của Scott không thực sự là một lời giải thích chút nào. Nó chỉ là một sự khẳng định của sự thật, được giải thích một cách khôn ngoan như một "lời giải thích". Ông nói rằng cách duy nhất để biết liệu nó là một mảng hay một đối tượng duy nhất là hỏi người dùng. Đây là, tất nhiên, không chính xác. Hoàn toàn có thể lưu trữ thông tin đó trong 'new []' và sau đó lấy lại trong 'delete', giống như nó được thực hiện ngay bây giờ với số phần tử. Quyết định đã được đưa ra chống lại nó, bởi vì nó sẽ quá phức tạp "hộ gia đình" thông tin cấu trúc và phân nhánh trong 'xóa'. Không có lời giải thích "xinh đẹp", nó đơn giản là * quyết định * theo cách đó. – AnT

+1

@ Jason: Không, lời giải thích của Scott hoàn toàn không có thật. Một lần nữa, làm thế nào để bạn biết có bao nhiêu phần tử trong mảng khi bạn thực hiện 'delete []'? Huh? Bạn có phải nói với nó để 'xóa []' chính mình? Số 'xóa []' "biết" nó bởi vì nó sử dụng thông tin hộ gia đình được chuẩn bị bởi 'new []'. Trong chính xác giống như cách chúng ta có thể bắt buộc 'new' \' new [] 'lưu trữ thông tin gia đình bổ sung về bản chất" mảng hay không ". Nó rất dễ dàng và rõ ràng. Đó là câu trả lời cho câu hỏi của bạn. – AnT

3

Heap tự biết kích thước của khối được phân bổ - bạn chỉ cần địa chỉ. Có vẻ như công cụ free() hoạt động - bạn chỉ truyền địa chỉ và giải phóng bộ nhớ.

Sự khác biệt giữa delete (delete[]) và free() là tên cũ trước hết gọi là destructors, sau đó là bộ nhớ trống (có thể sử dụng free()). Vấn đề là delete[] cũng chỉ có một đối số - địa chỉ và chỉ có địa chỉ đó cần phải biết số lượng đối tượng để chạy destructors.Vì vậy, new[] sử dụng cách thực hiện được xác định bằng cách nào đó để viết ở đâu đó số lượng các phần tử - thông thường nó sẽ thêm vào mảng với số lượng phần tử. Bây giờ xóa [] sẽ dựa vào dữ liệu thực hiện cụ thể đó để chạy các trình phá hủy và sau đó là bộ nhớ trống (một lần nữa, chỉ sử dụng địa chỉ khối).

+0

Không phải trình biên dịch cũng biết kích thước của các đối tượng được phân bổ (bao gồm cả phần đệm có thể). Theo cách này, với tổng kích thước của khối bộ nhớ được cấp phát, thời gian chạy sẽ có thể xác định xem nó có đang giải phóng một hoặc nhiều đối tượng trong một mảng hay không. – Giorgio

+0

@Giorgio: Trong các triển khai điển hình 'new []' prepends mảng với số lượng các phần tử và offset con trỏ sao cho nó trỏ vào phần tử thứ 0 của mảng, không phải là phần đầu của khối. Khi bạn gọi 'xóa' tất cả những gì bạn có là một địa chỉ được bù đắp và bạn không có phương tiện phổ quát nào để tìm ra điều đó. – sharptooth

-1

Điều này phức tạp hơn.

Từ khóa và quy ước sử dụng nó để xóa mảng được phát minh để thuận tiện cho việc triển khai và một số triển khai sử dụng nó (tôi không biết điều gì mặc dù MS VC++ không).

Sự tiện lợi là thế này:

Trong mọi trường hợp khác, bạn biết kích thước chính xác để được giải phóng bởi các phương tiện khác. Khi bạn xóa một đối tượng duy nhất, bạn có thể có kích thước từ thời gian biên dịch sizeof(). Khi bạn xóa một đối tượng đa hình bằng con trỏ cơ sở và bạn có một destructor ảo, bạn có thể có kích thước như một mục riêng biệt trong vtbl. Nếu bạn xóa một mảng, làm thế nào bạn sẽ biết kích thước của bộ nhớ được giải phóng, trừ khi bạn theo dõi nó một cách riêng biệt?

Cú pháp đặc biệt sẽ cho phép theo dõi kích thước như vậy chỉ cho một mảng - ví dụ, bằng cách đặt nó trước khi địa chỉ được trả về cho người dùng. Điều này chiếm thêm tài nguyên và không cần thiết cho các mảng không phải mảng.

+0

Đó là về theo dõi số lượng các yếu tố cần destructors của họ chạy, không phải số lượng bộ nhớ được phân bổ. –

1

delete[] chỉ cần gọi một triển khai khác (chức năng);

Không có lý do gì mà người cấp phát không thể theo dõi nó (thực tế, sẽ dễ dàng để bạn viết).

Tôi không biết lý do họ không quản lý hoặc lịch sử triển khai, nếu tôi đoán: Nhiều trong số này 'tốt, tại sao điều này không đơn giản hơn?' câu hỏi (trong C++) đi xuống một hoặc nhiều trong số:

  1. khả năng tương thích với C
  2. hiệu suất

Trong trường hợp này, hiệu suất. Sử dụng delete vs delete[] là dễ dàng đủ, tôi tin rằng tất cả có thể được trừu tượng từ lập trình viên và được hợp lý nhanh chóng (để sử dụng chung). delete[] chỉ yêu cầu một vài cuộc gọi và hoạt động bổ sung (bỏ qua các cuộc gọi hủy), nhưng đó là cho mỗi cuộc gọi để xóa và không cần thiết vì người lập trình thường biết loại người đó đang xử lý (nếu không, vấn đề ở bàn tay). Vì vậy, nó chỉ tránh gọi điện thoại thông qua các cấp phát. Ngoài ra, các phân bổ đơn lẻ này có thể không cần được theo dõi bởi người cấp phát càng nhiều chi tiết; Việc xử lý mọi phân bổ dưới dạng mảng sẽ yêu cầu các mục bổ sung để tính cho phân bổ tầm thường, do đó, nó là nhiều cấp độ đơn giản hóa việc thực hiện đơn giản, thực sự quan trọng đối với nhiều người, coi đó là miền cấp rất thấp.

9

Lý do chính tại sao nó được quyết định giữ riêng biệt deletedelete[] là hai thực thể này không giống với thực thể ở lần đầu tiên. Đối với một người quan sát ngây thơ, họ có vẻ gần như giống nhau: chỉ phá hủy và deallocate, với sự khác biệt duy nhất trong số lượng tiềm năng của các đối tượng để xử lý. Trong thực tế, sự khác biệt là quan trọng hơn nhiều.

Sự khác biệt quan trọng nhất giữa hai là delete có thể thực hiện xóa đa hình đối tượng, tức là loại tĩnh của đối tượng được đề cập có thể khác với loại động của nó.Mặt khác, delete[] phải đối phó với việc xóa hoàn toàn không đa hình các mảng. Vì vậy, trong nội bộ hai thực thể này thực hiện logic khác biệt đáng kể và không giao nhau giữa hai thực thể. Do khả năng xóa đa hình, chức năng của delete thậm chí không phải là từ xa giống như chức năng của delete[] trên một mảng của 1 phần tử, như một người quan sát ngây thơ có thể giả định không chính xác ban đầu.

Trái ngược với các yêu cầu lạ được đưa ra trong một số câu trả lời khác, tất nhiên, hoàn toàn có thể thay thế deletedelete[] chỉ với một cấu trúc duy nhất sẽ ở nhánh rất sớm, tức là nó sẽ xác định loại khối bộ nhớ (mảng hoặc không) sử dụng thông tin hộ gia đình sẽ được lưu trữ bởi new/new[] và sau đó chuyển đến chức năng thích hợp, tương đương với delete hoặc delete[]. Tuy nhiên, đây sẽ là một quyết định thiết kế khá kém, vì, một lần nữa, chức năng của cả hai là quá khác nhau. Buộc cả hai thành một cấu trúc đơn lẻ sẽ giống như việc tạo ra một Con dao quân đội Thụy Sĩ của một chức năng deallocation. Ngoài ra, để có thể nói một mảng từ một mảng không, chúng tôi sẽ phải giới thiệu thêm một phần thông tin hộ gia đình ngay cả vào phân bổ bộ nhớ một đối tượng được thực hiện với đồng bằng new. Điều này có thể dễ dàng dẫn đến chi phí bộ nhớ đáng chú ý trong phân bổ đối tượng đơn lẻ.

Nhưng, một lần nữa, lý do chính ở đây là sự khác biệt chức năng giữa deletedelete[]. Những thực thể ngôn ngữ này chỉ có sự tương đồng sâu sắc về da chỉ tồn tại ở mức độ đặc tả ngây thơ ("hủy diệt và bộ nhớ tự do"), nhưng một khi người ta hiểu chi tiết những thực thể này thực sự phải làm gì thì nhận ra rằng chúng quá khác nhau được hợp nhất thành một.

P.S. Đây là BTW một trong những vấn đề với đề xuất về sizeof(type) bạn đã thực hiện trong câu hỏi. Do tính chất đa hình có khả năng của delete, bạn không biết loại trong số delete, đó là lý do tại sao bạn không thể nhận bất kỳ số sizeof(type) nào. Có nhiều vấn đề hơn với ý tưởng này, nhưng điều đó đã đủ để giải thích tại sao nó không bay.

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