Giả sử tôi có mã sau:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Cái này có an toàn không? Hay phải ptr
được chuyển đến char*
trước khi xóa?
Giả sử tôi có mã sau:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Cái này có an toàn không? Hay phải ptr
được chuyển đến char*
trước khi xóa?
Câu trả lời:
Nó phụ thuộc vào "an toàn." Nó thường sẽ hoạt động vì thông tin được lưu trữ cùng với con trỏ về bản thân việc phân bổ, vì vậy bộ phân bổ giao dịch có thể trả nó về đúng vị trí. Theo nghĩa này, nó là "an toàn" miễn là trình phân bổ của bạn sử dụng các thẻ ranh giới nội bộ. (Nhiều làm.)
Tuy nhiên, như đã đề cập trong các câu trả lời khác, việc xóa một con trỏ void sẽ không gọi hàm hủy, điều này có thể là một vấn đề. Theo nghĩa đó, nó không phải là "an toàn."
Không có lý do chính đáng để làm những gì bạn đang làm theo cách bạn đang làm. Nếu bạn muốn viết các hàm định vị của riêng mình, bạn có thể sử dụng các mẫu hàm để tạo các hàm với kiểu chính xác. Một lý do chính đáng để làm điều đó là tạo bộ phân bổ nhóm, có thể cực kỳ hiệu quả cho các loại cụ thể.
Như đã đề cập trong các câu trả lời khác, đây là hành vi không xác định trong C ++. Nói chung là tốt để tránh hành vi không xác định, mặc dù bản thân chủ đề này rất phức tạp và chứa đựng nhiều ý kiến trái chiều.
sizeof(T*) == sizeof(U*)
đối với tất cả các T,U
gợi ý rằng có thể có 1 void *
triển khai bộ thu gom rác dựa trên khuôn mẫu . Nhưng sau đó, khi gc thực sự phải xóa / giải phóng một con trỏ chính xác thì câu hỏi này sẽ nảy sinh. Để làm cho nó hoạt động, bạn cần có trình bao bọc hàm hủy hàm lambda (urgh) hoặc bạn sẽ cần một số loại động "loại như dữ liệu" cho phép qua lại giữa một loại và một cái gì đó có thể lưu trữ.
Xóa thông qua con trỏ void không được xác định bởi Tiêu chuẩn C ++ - xem phần 5.3.5 / 3:
Trong phương án thay thế đầu tiên (đối tượng xóa), nếu kiểu tĩnh của toán hạng khác với kiểu động của nó, kiểu tĩnh sẽ là lớp cơ sở của kiểu động của toán hạng và kiểu tĩnh phải có bộ hủy ảo hoặc hành vi là không xác định . Trong giải pháp thay thế thứ hai (xóa mảng) nếu kiểu động của đối tượng cần xóa khác với kiểu tĩnh của nó, thì hành vi đó là không xác định.
Và chú thích của nó:
Điều này ngụ ý rằng không thể xóa một đối tượng bằng con trỏ kiểu void * vì không có đối tượng nào thuộc kiểu void
.
NULL
tạo ra sự khác biệt nào cho việc quản lý bộ nhớ ứng dụng?
Đó không phải là một ý tưởng hay và không phải là điều bạn sẽ làm trong C ++. Bạn đang mất thông tin loại của bạn mà không có lý do.
Hàm hủy của bạn sẽ không được gọi trên các đối tượng trong mảng mà bạn đang xóa khi bạn gọi nó cho các kiểu không nguyên thủy.
Thay vào đó, bạn nên ghi đè mới / xóa.
Việc xóa khoảng trống * có thể tình cờ sẽ giải phóng bộ nhớ của bạn một cách chính xác, nhưng sai vì kết quả không được xác định.
Nếu vì lý do nào đó mà tôi không biết, bạn cần lưu trữ con trỏ của mình trong một khoảng trống * rồi giải phóng nó, bạn nên sử dụng malloc và miễn phí.
Câu hỏi không có ý nghĩa. Sự nhầm lẫn của bạn một phần có thể do ngôn ngữ cẩu thả mà mọi người thường sử dụng delete
:
Bạn sử dụng delete
để hủy một đối tượng đã được cấp phát động. Làm như vậy, bạn tạo một biểu thức xóa với một con trỏ đến đối tượng đó . Bạn không bao giờ "xóa một con trỏ". Những gì bạn thực sự làm là "xóa một đối tượng được xác định bằng địa chỉ của nó".
Bây giờ chúng ta thấy tại sao câu hỏi không có ý nghĩa: Con trỏ void không phải là "địa chỉ của một đối tượng". Nó chỉ là một địa chỉ, không có bất kỳ ngữ nghĩa nào. Nó có thể đến từ địa chỉ của một đối tượng thực tế, nhưng thông tin đó bị mất, vì nó được mã hóa theo kiểu con trỏ ban đầu. Cách duy nhất để khôi phục một con trỏ đối tượng là chuyển con trỏ void trở lại một con trỏ đối tượng (điều này yêu cầu tác giả biết ý nghĩa của con trỏ). void
bản thân nó là một kiểu không hoàn chỉnh và do đó không bao giờ là kiểu của một đối tượng, và con trỏ void không bao giờ có thể được sử dụng để xác định một đối tượng. (Các đối tượng được xác định chung theo loại và địa chỉ của chúng.)
delete
có thể là giá trị con trỏ null, một con trỏ đến một đối tượng không phải mảng được tạo bởi một biểu thức mới trước đó hoặc một con trỏ tới một subobject đại diện cho một lớp cơ sở của một đối tượng như vậy. Nếu không, hành vi là không xác định. " Vì vậy, nếu một trình biên dịch mã của bạn chấp nhận mà không có một chẩn đoán, nó không có gì nhưng một lỗi trong trình biên dịch ...
delete void_pointer
. Đó là hành vi không xác định. Lập trình viên không bao giờ được gọi hành vi không xác định, ngay cả khi phản hồi dường như thực hiện những gì lập trình viên muốn thực hiện.
Nếu bạn thực sự phải làm điều này, tại sao không cắt bỏ người trung gian ( new
và các delete
nhà điều hành) và gọi toàn cầu operator new
và operator delete
trực tiếp? (Tất nhiên, nếu bạn đang cố gắng đo lường các toán tử new
và delete
, bạn thực sự phải thực hiện lại operator new
và operator delete
.)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
Lưu ý rằng không giống như malloc()
, operator new
ném std::bad_alloc
khi thất bại (hoặc gọi new_handler
nếu một trong những đã được đăng ký).
Bởi vì char không có logic hủy đặc biệt. Điều này sẽ không hoạt động.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
Ông sẽ không được gọi.
Nếu bạn muốn sử dụng void *, tại sao bạn không chỉ sử dụng malloc / free? new / delete không chỉ là quản lý bộ nhớ. Về cơ bản, new / delete gọi một hàm tạo / hủy và có nhiều thứ khác đang diễn ra. Nếu bạn chỉ sử dụng các kiểu tích hợp sẵn (như char *) và xóa chúng qua void *, nó sẽ hoạt động nhưng vẫn không được khuyến khích. Điểm mấu chốt là sử dụng malloc / free nếu bạn muốn sử dụng void *. Nếu không, bạn có thể sử dụng các hàm mẫu để thuận tiện cho bạn.
template<typename T>
T* my_alloc (size_t size)
{
return new T [size];
}
template<typename T>
void my_free (T* ptr)
{
delete [] ptr;
}
int main(void)
{
char* pChar = my_alloc<char>(10);
my_free(pChar);
}
Rất nhiều người đã nhận xét rằng không, không an toàn khi xóa con trỏ void. Tôi đồng ý với điều đó, nhưng tôi cũng muốn nói thêm rằng nếu bạn đang làm việc với con trỏ void để phân bổ các mảng liền kề hoặc thứ gì đó tương tự, bạn có thể làm điều này với new
để có thể sử dụng delete
an toàn (với, ahem , một chút công việc phụ). Điều này được thực hiện bằng cách phân bổ một con trỏ rỗng vào vùng nhớ (được gọi là 'đấu trường') và sau đó cung cấp con trỏ tới đấu trường thành mới. Xem phần này trong Câu hỏi thường gặp về C ++ . Đây là một cách tiếp cận phổ biến để triển khai vùng nhớ trong C ++.
Hầu như không có lý do để làm điều này.
Trước hết, nếu bạn không biết loại của dữ liệu, và tất cả các bạn biết là nó void*
, sau đó bạn thực sự chỉ nên được điều trị mà dữ liệu như một typeless blob của dữ liệu nhị phân ( unsigned char*
), và sử dụng malloc
/ free
để đối phó với nó . Điều này đôi khi được yêu cầu đối với những thứ như dữ liệu dạng sóng và những thứ tương tự, nơi bạn cần chuyển các void*
con trỏ đến C apis. Tốt rồi.
Nếu bạn làm biết loại dữ liệu (tức là nó có một ctor / dtor), nhưng đối với một số lý do bạn đã kết thúc với một void*
con trỏ (vì lý do gì bạn có) sau đó bạn thực sự nên bỏ nó trở lại kiểu bạn biết điều đó để được , và kêu gọi delete
nó.
Tôi đã sử dụng void *, (hay còn gọi là các loại không xác định) trong khuôn khổ của mình trong khi phản chiếu mã và các kỳ công khác về sự mơ hồ, và cho đến nay, tôi không gặp rắc rối nào (rò rỉ bộ nhớ, vi phạm quyền truy cập, v.v.) từ bất kỳ trình biên dịch nào. Chỉ cảnh báo do hoạt động không chuẩn.
Hoàn toàn hợp lý khi xóa một không xác định (void *). Chỉ cần đảm bảo rằng con trỏ tuân theo các nguyên tắc sau, nếu không nó có thể ngừng hoạt động:
1) Con trỏ không xác định không được trỏ đến một kiểu có giải mã lệnh tầm thường, và vì vậy khi được ép như một con trỏ không xác định, nó sẽ KHÔNG BAO GIỜ BỊ XÓA. Chỉ xóa con trỏ không xác định SAU KHI truyền nó trở lại kiểu GỐC.
2) Cá thể đang được tham chiếu như một con trỏ không xác định trong ngăn xếp bị ràng buộc hoặc bộ nhớ liên kết đống? Nếu con trỏ không xác định tham chiếu đến một cá thể trên ngăn xếp, thì nó KHÔNG BAO GIỜ BỊ XÓA!
3) Bạn có khẳng định 100% con trỏ không xác định là vùng bộ nhớ hợp lệ không? Không, vậy thì nó KHÔNG BAO GIỜ BỊ XÓA!
Nói chung, có rất ít công việc trực tiếp có thể được thực hiện bằng cách sử dụng kiểu con trỏ (void *) không xác định. Tuy nhiên, một cách gián tiếp, khoảng trống * là một tài sản tuyệt vời cho các nhà phát triển C ++ dựa vào khi cần phải nhập nhằng dữ liệu.
Nếu bạn chỉ muốn một bộ đệm, hãy sử dụng malloc / free. Nếu bạn phải sử dụng new / delete, hãy xem xét một lớp wrapper tầm thường:
template<int size_ > struct size_buffer {
char data_[ size_];
operator void*() { return (void*)&data_; }
};
typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer
OpaqueBuffer* ptr = new OpaqueBuffer();
delete ptr;
Đối với trường hợp cụ thể của char.
char là một loại nội tại không có hàm hủy đặc biệt. Vì vậy, các đối số rò rỉ là một cuộc tranh luận.
sizeof (char) thường là một vì vậy cũng không có đối số căn chỉnh. Trong trường hợp hiếm nền tảng mà sizeof (char) không phải là một, chúng phân bổ bộ nhớ được căn chỉnh đủ cho char của chúng. Vì vậy, đối số căn chỉnh cũng là một tranh luận.
malloc / free sẽ nhanh hơn trong trường hợp này. Nhưng bạn đã mất std :: bad_alloc và phải kiểm tra kết quả của malloc. Gọi các toán tử mới và xóa toàn cầu có thể tốt hơn vì nó bỏ qua người trung gian.
new
thực sự được định nghĩa để ném. Đây không phải là sự thật. Nó phụ thuộc vào trình biên dịch và chuyển đổi trình biên dịch. Xem ví dụ /GX[-] enable C++ EH (same as /EHsc)
công tắc MSVC2019 . Ngoài ra, trên các hệ thống nhúng, nhiều người chọn không trả thuế hiệu suất cho các ngoại lệ C ++. Vì vậy, câu bắt đầu bằng "But you forfeit std :: bad_alloc ..." là đáng nghi vấn.