Mendeteksi kesalahan penggunaan delete [] vs. delete pada waktu kompilasi

19

Saya ingin tahu apakah mungkin mendeteksi deletekesalahan yang dikomentari di bawah ini pada waktu kompilasi? Terutama, saya ingin mendengar tentang kompiler g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  
SebGR
sumber
7
Anda seharusnya tidak memanggil delete secara manual.
Martin York
9
@LokiAstari Apakah Anda benar-benar berpikir bahwa komentar itu membantu?
James
5
@ James: Ya. Kuncinya adalah "secara manual".
Martin York
Kadang-kadang untuk mematuhi ini akan melibatkan penulisan ulang banyak kode warisan
Nick Keighley
Gunakan std::unique_ptr<ClassTypeA[]>dan kemudian Anda tidak perlu melakukannya.
user253751

Jawaban:

6

Secara umum, kompiler tidak dapat mendeteksi kesalahan seperti itu. Contoh: Misalkan konstruktor untuk beberapa kelas mengalokasikan beberapa anggota data menggunakan new TypeName[], tetapi destruktor salah menggunakan deletesebagai gantinya delete[]. Jika konstruktor dan destruktor didefinisikan dalam unit kompilasi terpisah, bagaimana kompiler mengetahui ketika mengkompilasi file yang mendefinisikan destruktor bahwa penggunaannya tidak konsisten dengan yang ada di file yang dikompilasi secara terpisah yang mendefinisikan konstruktor?

Berkenaan dengan kompiler GNU, itu tidak. Seperti disebutkan di atas, tidak dapat melakukannya dalam kasus umum. Kompiler tidak harus mendeteksi kesalahan baru / hapus yang tidak cocok karena ini adalah perilaku yang tidak terdefinisi. UB adalah kartu "keluar dari penjara" dari vendor kompiler.

Alat seperti valgrind dapat dan memang mendeteksi ketidakcocokan baru / hapus ini, tetapi melakukannya saat runtime. Mungkin ada alat analisis statis yang melihat semua file sumber yang pada akhirnya akan dikompilasi untuk membentuk executable, tapi saya tidak menemukan alat analisis statis yang mendeteksi kesalahan semacam ini.

David Hammen
sumber
Saya telah menggunakan alat analisis statis bernama Parasoft yang pasti memiliki aturan untuk skenario khusus ini. Itu berjalan di semua file dalam proyek tertentu (jika sudah dikonfigurasi dengan benar). Yang sedang berkata, saya tidak yakin seberapa baik menangani skenario seperti komentar Pete Kirkham pada jawaban Kilian Foth.
Velociraptors
28

Anda dapat menggunakan kelas RAII yang sesuai untuk delete. Ini adalah satu-satunya cara aman untuk melakukannya, dan kesalahan ini hanya salah satu dari sangat, sangat banyak yang Anda temui menyebut deletediri Anda.

Selalu gunakan kelas untuk mengelola sumber daya seumur hidup yang dinamis, dan sistem tipe akan menegakkan penghancuran sumber daya yang benar.

Sunting: "Bagaimana jika Anda mengaudit kode dan tidak dapat mengubahnya?" Anda kacau.

DeadMG
sumber
18
-1 karena ini sebenarnya tidak menjawab pertanyaan.
Mason Wheeler
2
Satu-satunya cara untuk mendeteksi ketidakcocokan adalah dengan menggunakan sistem tipe, yang melibatkan penggunaan kelas RAII.
DeadMG
9
... itu bahkan lebih tidak masuk akal. Apa yang harus dilakukan oleh penggunaan kelas RAII - mekanisme runtime - dengan informasi sistem tipe statis yang diketahui oleh kompiler pada waktu kompilasi?
Mason Wheeler
6
@MasonWheeler lihat boost :: shared_ptr dan boost :: shared_array sebagai contoh. Menghancurkan shared_ptr menghapus objek, menghancurkan shared_array menghapus [] s array. Anda tidak dapat menetapkan shared_array ke shared_ptr, jadi - selama Anda tidak membangun shared_ptr dengan array di tempat pertama - sistem tipe mencegah penghapusan salah yang digunakan.
Pete Kirkham
4
Biasanya, jawaban seperti ini lebih menjengkelkan daripada membantu. Namun, dalam kasus ini, itu sebenarnya benar. Dia mencari penegakan compiler dari kesalahan umum, dan menggunakan RAII dengan benar mencegah gaya kesalahan ini, dengan demikian memberinya apa yang dia inginkan. +1
riwalk
10

Kesalahan khusus ini - ya. Ini jenis kesalahan umum: sayangnya, tidak ada! Itu akan melibatkan memprediksi aliran eksekusi tanpa benar-benar mengeksekusinya, dan itu tidak mungkin untuk program sewenang-wenang. (Itu sebabnya kebanyakan kompiler bahkan tidak mencoba mendeteksi kasus sederhana seperti contoh Anda.)

Karena itu jawaban DeadMG adalah yang tepat: jangan mencoba untuk memperbaikinya dengan memperhatikan - perhatian manusia bisa keliru. Gunakan sarana yang disediakan bahasa dan biarkan komputer memperhatikan.

Kilian Foth
sumber
Bagaimana ini membutuhkan prediksi aliran eksekusi? Bagi saya ini kelihatannya benar-benar statis, pengetahuan waktu kompilasi bagi saya; sistem tipe kompiler tahu apa itu array dan apa yang bukan.
Mason Wheeler
Bahkan di hadapan para pemain? Maaf, jika saya salah, saya akan menghapus jawabannya.
Kilian Foth
12
@MasonWheeler jenis abc_ptr statis ClassTypeA*sehingga Anda dapat menyisipkan garis antara yang baru dan yang menghapus if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Tidak ada dalam sistem tipe statis yang menunjukkan apakah abc_ptrmenunjuk ke array atau objek dinamis atau bagian jalan ke objek atau array lain.
Pete Kirkham
... oh benar Saya sangat terbiasa bekerja dengan bahasa dengan tipe array nyata sehingga saya lupa bagaimana mengacaukannya di C-land. :(
Mason Wheeler
1
@Pete Kirkham, @Mason Wheeler: Tapi tetap saja, runtime akan melihat berapa banyak objek yang disimpan di alamat yang ditunjuk oleh abc_ptr, jika tidak, bagaimana bisa deallocate jumlah memori yang tepat? Jadi runtime tahu berapa banyak objek yang harus di-deallocated.
Giorgio
4

Kasus sepele yang Anda tunjukkan dapat dideteksi pada waktu kompilasi, karena instantiasi dan penghancuran objek berada dalam cakupan yang sama. Secara umum, penghapusan tidak ada dalam cakupan yang sama, atau bahkan file sumber yang sama, seperti instantiasi. Dan tipe C ++ pointer tidak membawa informasi tentang apakah referensi objek tunggal dari jenis atau array, apalagi skema alokasi. Jadi tidak mungkin untuk mendiagnosis ini pada waktu kompilasi secara umum.

Mengapa tidak mendiagnosis kasus khusus yang mungkin terjadi?

Di C ++ sudah ada alat untuk menangani kebocoran sumber daya dinamis yang terkait dengan cakupan, yaitu smart pointer dan array level yang lebih tinggi ( std::vector).

Bahkan jika Anda menggunakan deleterasa yang benar , kode Anda masih tidak terkecuali aman. Jika kode antara new[]dan delete[]diakhiri dengan keluar dinamis, penghapusan tidak pernah dijalankan.

Sejauh deteksi run-time berjalan, Valgrindalat melakukan pekerjaan yang baik untuk mendeteksi ini pada saat run-time. Menonton:

==26781== Command: ./a.out
==26781==
==26781== Mismatched free() / delete / delete []
==26781==    at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048498: main (in /home/kaz/test/a.out)
==26781==  Address 0x4324028 is 0 bytes inside a block of size 80 alloc'd
==26781==    at 0x402B454: operator new[](unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048488: main (in /home/kaz/test/a.out)

Tentu saja, Valgrind tidak berjalan pada semua platform, dan tidak selalu praktis atau mungkin untuk mereproduksi semua situasi run-time di bawah alat ini.

Kaz
sumber
Anda mengatakan bahwa kasus sepele ini dapat dideteksi pada waktu kompilasi. Bisakah Anda memberi tahu saya perintah kompilasi apa yang Anda gunakan untuk mencapainya?
SebGR
"dapat dideteksi pada waktu kompilasi" di sini berarti mudah untuk diimplementasikan dalam kompiler, bukan g ++ yang memilikinya. Kompiler memiliki seluruh masa pakai pengidentifikasi dalam genggamannya saat memproses ruang lingkup itu, dan dapat menyebarkan informasi alokasi sebagai atribut semantik yang dikaitkan dengan sintaksis.
Kaz
-3

Beberapa contoh deteksi yang sepele pada waktu kompilasi / analisis-statis:

Pada host RHEL7 dengan cppcheck 1.77 and 1.49

> cat test.cc
#include <memory>
int main(){char* buf = new char[10];delete buf;}

http://cppcheck.sourceforge.net/

> cppcheck -x c++ test.cc
Checking test.cc ...
[test.cc:2]: (error) Mismatching allocation and deallocation: buf

Dengan clang++ 3.7.1di RHEL7

> clang++ --analyze -x c++ test.cc
test.cc:2:37: warning: Memory allocated by 'new[]' should be deallocated by
'delete[]', not 'delete'
int main(){char* buf = new char[10];delete buf;}
                                    ^~~~~~~~~~
1 warning generated.

The Clang Static Analyzer juga dapat mendeteksi kapan std::unique_ptrtidak lulus<char[]>

> cat test2.cc
#include <memory>
int main(){std::unique_ptr<char> buf(new char[10]);}

https://clang-analyzer.llvm.org/

> clang++ --analyze -x c++ -std=c++11 test2.cc
In file included from test2.cc:1:
In file included from /opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/memory:81:
/opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/bits/unique_ptr.h:76:2: 
warning: Memory allocated by
      'new[]' should be deallocated by 'delete[]', not 'delete'
        delete __ptr;
        ^~~~~~~~~~~~
1 warning generated.

Perbarui di bawah ini dengan tautan ke pekerjaan yang menambahkan ini ke dentang, tes dan satu bug yang saya temukan.

Ini ditambahkan ke dentang dengan reviews.llvm.org/D4661 - "Deteksi ketidakcocokan penggunaan 'baru' dan 'hapus'" .

Tes dalam tes / Analisis / MismatchedDeallocator-checker-test.mm

Saya menemukan bug terbuka ini - bugs.llvm.org/show_bug.cgi?id=24819

ituafunnyname
sumber
Tidak ada yang meragukan bahwa Anda dapat menemukan penganalisa statis yang mendeteksi satu penggunaan yang salah spesifik , alih-alih yang mendeteksi semua penggunaan yang salah (dan mudah-mudahan kesalahan penggunaan tidak benar )
Caleth