Misalkan saya memiliki kode berikut:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Apakah ini aman? Atau harus ptr
dilemparkan ke char*
sebelum dihapus?
Misalkan saya memiliki kode berikut:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Apakah ini aman? Atau harus ptr
dilemparkan ke char*
sebelum dihapus?
Jawaban:
Itu tergantung pada "aman." Ini biasanya akan berfungsi karena informasi disimpan bersama dengan penunjuk tentang alokasi itu sendiri, sehingga deallocator dapat mengembalikannya ke tempat yang tepat. Dalam pengertian ini "aman" selama pengalokasi Anda menggunakan tag batas internal. (Banyak yang melakukannya.)
Namun, seperti yang disebutkan dalam jawaban lain, menghapus void pointer tidak akan memanggil destruktor, yang bisa menjadi masalah. Dalam pengertian itu, ini tidak "aman."
Tidak ada alasan yang baik untuk melakukan apa yang Anda lakukan dengan cara Anda melakukannya. Jika Anda ingin menulis fungsi deallokasi Anda sendiri, Anda dapat menggunakan template fungsi untuk menghasilkan fungsi dengan jenis yang benar. Alasan yang baik untuk melakukannya adalah untuk menghasilkan pengalokasi kumpulan, yang bisa sangat efisien untuk tipe tertentu.
Seperti yang disebutkan dalam jawaban lain, ini adalah perilaku tidak terdefinisi di C ++. Secara umum adalah baik untuk menghindari perilaku yang tidak terdefinisi, meskipun topiknya sendiri rumit dan penuh dengan pendapat yang saling bertentangan.
sizeof(T*) == sizeof(U*)
untuk semua T,U
menyarankan bahwa itu harus mungkin untuk memiliki 1 non template, void *
implementasi pengumpul sampah berbasis. Tapi kemudian, ketika gc benar-benar harus menghapus / membebaskan pointer, pertanyaan ini muncul. Untuk membuatnya bekerja, Anda memerlukan lambda function destructor wrappers (urgh) atau Anda akan membutuhkan semacam "tipe sebagai data" yang dinamis yang memungkinkan bolak-balik antara suatu tipe dan sesuatu yang dapat disimpan.
Menghapus melalui penunjuk kosong tidak ditentukan oleh Standar C ++ - lihat bagian 5.3.5 / 3:
Dalam alternatif pertama (hapus objek), jika tipe statis operan berbeda dari tipe dinamisnya, tipe statis harus kelas dasar tipe dinamis operan dan tipe statis harus memiliki destruktor virtual atau perilakunya tidak ditentukan . Di alternatif kedua (hapus array) jika tipe dinamis dari objek yang akan dihapus berbeda dari tipe statisnya, perilakunya tidak ditentukan.
Dan catatan kakinya:
Ini menyiratkan bahwa suatu objek tidak dapat dihapus menggunakan penunjuk berjenis void * karena tidak ada objek berjenis void
.
NULL
membuat perbedaan untuk manajemen memori aplikasi?
Ini bukan ide yang bagus dan bukan sesuatu yang akan Anda lakukan di C ++. Anda kehilangan info tipe Anda tanpa alasan.
Destruktor Anda tidak akan dipanggil pada objek dalam larik yang Anda hapus saat Anda memanggilnya untuk tipe non primitif.
Anda sebaiknya mengganti baru / hapus.
Menghapus void * mungkin akan membebaskan memori Anda dengan benar secara kebetulan, tetapi itu salah karena hasilnya tidak ditentukan.
Jika karena alasan tertentu tidak saya ketahui Anda perlu menyimpan pointer Anda dalam void * lalu membebaskannya, Anda harus menggunakan malloc dan gratis.
Pertanyaan itu tidak masuk akal. Kebingungan Anda mungkin sebagian karena bahasa ceroboh yang sering digunakan orang dengan delete
:
Anda gunakan delete
untuk menghancurkan objek yang dialokasikan secara dinamis. Lakukan itu, Anda membentuk ekspresi hapus dengan penunjuk ke objek itu . Anda tidak pernah "menghapus penunjuk". Apa yang sebenarnya Anda lakukan adalah "menghapus sebuah objek yang dikenali dari alamatnya".
Sekarang kita melihat mengapa pertanyaan itu tidak masuk akal: Void pointer bukanlah "alamat sebuah objek". Itu hanya sebuah alamat, tanpa semantik apapun. Ini mungkin berasal dari alamat objek sebenarnya, tetapi informasi itu hilang, karena dikodekan dalam tipe penunjuk asli. Satu-satunya cara untuk memulihkan penunjuk objek adalah dengan mengembalikan penunjuk kosong ke penunjuk objek (yang mengharuskan penulis untuk mengetahui apa arti penunjuk). void
itu sendiri adalah tipe yang tidak lengkap dan karenanya tidak pernah menjadi tipe objek, dan penunjuk kosong tidak pernah dapat digunakan untuk mengidentifikasi objek. (Objek diidentifikasi secara bersama-sama berdasarkan jenis dan alamatnya.)
delete
mungkin berupa nilai penunjuk nol, penunjuk ke objek non-larik yang dibuat oleh ekspresi baru sebelumnya , atau penunjuk ke subobjek yang mewakili kelas dasar dari objek semacam itu. Jika tidak, perilakunya tidak ditentukan. " Jadi jika kompiler menerima kode Anda tanpa diagnostik, itu hanyalah bug di kompiler ...
delete void_pointer
. Ini perilaku yang tidak terdefinisi. Pemrogram tidak boleh meminta perilaku tidak terdefinisi, bahkan jika responsnya tampak melakukan apa yang diinginkan oleh pemrogram.
Jika Anda benar-benar harus melakukan ini, mengapa tidak memotong tengah-tengah manusia (yang new
dan delete
operator) dan panggilan global operator new
dan operator delete
langsung? (Tentu saja, jika Anda mencoba melengkapi operator new
dan delete
, Anda sebenarnya harus menerapkan ulang operator new
dan operator delete
.)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
Perhatikan bahwa tidak seperti malloc()
, operator new
melempar std::bad_alloc
pada kegagalan (atau memanggil new_handler
jika ada yang terdaftar).
Karena char tidak memiliki logika destruktor khusus. INI tidak akan berhasil.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
D'ctor tidak akan dipanggil.
Jika Anda ingin menggunakan void *, mengapa Anda tidak menggunakan malloc / gratis saja? baru / hapus lebih dari sekedar pengelolaan memori. Pada dasarnya, new / delete memanggil konstruktor / destruktor dan ada lebih banyak hal yang terjadi. Jika Anda hanya menggunakan tipe bawaan (seperti char *) dan menghapusnya melalui void *, ini akan berhasil tetapi tetap tidak disarankan. Intinya adalah gunakan malloc / gratis jika Anda ingin menggunakan void *. Jika tidak, Anda dapat menggunakan fungsi template untuk kenyamanan Anda.
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);
}
Banyak orang telah berkomentar mengatakan tidak, tidak aman untuk menghapus penunjuk kosong. Saya setuju dengan itu, tetapi saya juga ingin menambahkan bahwa jika Anda bekerja dengan void pointer untuk mengalokasikan array yang berdekatan atau yang serupa, Anda dapat melakukan ini new
sehingga Anda dapat menggunakan delete
dengan aman (dengan, ahem , sedikit kerja ekstra). Ini dilakukan dengan mengalokasikan penunjuk kosong ke wilayah memori (disebut 'arena') dan kemudian memasok penunjuk ke arena ke yang baru. Lihat bagian ini di FAQ C ++ . Ini adalah pendekatan umum untuk mengimplementasikan kumpulan memori di C ++.
Hampir tidak ada alasan untuk melakukan ini.
Pertama-tama, jika Anda tidak tahu jenis datanya, dan yang Anda tahu hanyalah itu void*
, maka Anda seharusnya memperlakukan data itu sebagai gumpalan data biner ( unsigned char*
) tanpa tipe , dan gunakan malloc
/ free
untuk menghadapinya . Ini kadang-kadang diperlukan untuk hal-hal seperti data bentuk gelombang dan sejenisnya, di mana Anda perlu meneruskan void*
petunjuk ke C apis. Tidak apa-apa.
Jika Anda melakukan mengetahui jenis data (yaitu memiliki ctor / dtor), tapi untuk beberapa alasan Anda berakhir dengan void*
pointer (karena alasan apapun Anda memiliki) maka Anda benar-benar harus melemparkan kembali ke jenis yang Anda tahu itu untuk jadilah , dan panggil delete
itu.
Saya telah menggunakan void *, (alias tipe yang tidak diketahui) dalam kerangka saya untuk sementara dalam refleksi kode dan prestasi ambiguitas lainnya, dan sejauh ini, saya tidak memiliki masalah (kebocoran memori, pelanggaran akses, dll.) Dari kompiler mana pun. Hanya peringatan karena operasinya tidak standar.
Sangat masuk akal untuk menghapus yang tidak diketahui (void *). Pastikan saja penunjuk mengikuti pedoman ini, atau mungkin berhenti masuk akal:
1) Penunjuk yang tidak diketahui tidak boleh menunjuk ke tipe yang memiliki dekonstruktor sepele, dan ketika dicor sebagai penunjuk yang tidak diketahui, itu TIDAK PERNAH DIHAPUS. Hapus hanya penunjuk yang tidak dikenal SETELAH mentransmisikannya kembali ke tipe ASLI.
2) Apakah instance direferensikan sebagai pointer yang tidak dikenal dalam memori terikat tumpukan atau tumpukan? Jika pointer yang tidak diketahui mereferensikan sebuah instance pada stack, maka itu TIDAK PERNAH DIHAPUS!
3) Apakah Anda 100% positif pointer yang tidak diketahui adalah wilayah memori yang valid? Tidak, maka itu TIDAK PERNAH DITUNDA!
Secara keseluruhan, sangat sedikit pekerjaan langsung yang dapat dilakukan dengan menggunakan tipe penunjuk yang tidak diketahui (void *). Namun, secara tidak langsung, void * adalah aset yang bagus untuk diandalkan oleh developer C ++ saat ambiguitas data diperlukan.
Jika Anda hanya ingin buffer, gunakan malloc / free. Jika Anda harus menggunakan new / delete, pertimbangkan kelas pembungkus sepele:
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;
Untuk kasus khusus char.
char adalah tipe intrinsik yang tidak memiliki destruktor khusus. Jadi argumen kebocoran itu bisa diperdebatkan.
sizeof (char) biasanya satu jadi tidak ada argumen penyelarasan juga. Dalam kasus platform langka di mana sizeof (char) bukan satu, mereka mengalokasikan memori yang cukup untuk karakter mereka. Jadi argumen penyelarasan juga bisa diperdebatkan.
malloc / free akan lebih cepat dalam kasus ini. Tetapi Anda kehilangan std :: bad_alloc dan harus memeriksa hasil malloc. Memanggil operator baru dan hapus global mungkin lebih baik karena melewati perantara.
new
sebenarnya adalah melempar. Ini tidak benar. Ini tergantung pada kompiler dan kompilator. Lihat misalnya /GX[-] enable C++ EH (same as /EHsc)
sakelar MSVC2019 . Juga pada sistem tertanam banyak yang memilih untuk tidak membayar pajak kinerja untuk pengecualian C ++. Jadi kalimat yang diawali dengan "But you forfeit std :: bad_alloc ..." patut dipertanyakan.