Apa saja tips umum untuk memastikan saya tidak membocorkan memori dalam program C ++? Bagaimana cara mengetahui siapa yang harus membebaskan memori yang telah dialokasikan secara dinamis?
c++
memory
memory-management
raii
dulipishi
sumber
sumber
Jawaban:
Alih-alih mengelola memori secara manual, coba gunakan pointer pintar jika ada.
Lihatlah Boost lib , TR1 , dan smart pointer .
Smart pointer juga sekarang menjadi bagian dari standar C ++ yang disebut C ++ 11 .
sumber
Saya benar-benar mendukung semua saran tentang RAII dan smart pointer, tetapi saya juga ingin menambahkan tip level yang sedikit lebih tinggi: memori termudah untuk dikelola adalah memori yang tidak pernah Anda alokasikan. Tidak seperti bahasa seperti C # dan Java, di mana semuanya adalah referensi, di C ++ Anda harus meletakkan objek di tumpukan kapan pun Anda bisa. Seperti yang saya lihat beberapa orang (termasuk Dr Stroustrup) tunjukkan, alasan utama mengapa pengumpulan sampah tidak pernah populer di C ++ adalah bahwa C ++ yang ditulis dengan baik tidak menghasilkan banyak sampah di tempat pertama.
Jangan menulis
atau bahkan
ketika Anda bisa menulis
sumber
Gunakan RAII
Posting ini tampaknya berulang, tetapi dalam C ++, pola paling dasar untuk diketahui adalah RAII .
Belajar menggunakan pointer pintar, baik dari boost, TR1 atau bahkan auto_ptr yang rendah (tapi seringkali cukup efisien) (tetapi Anda harus tahu keterbatasannya).
RAII adalah dasar dari pengecualian keselamatan dan pembuangan sumber daya di C ++, dan tidak ada pola lain (roti lapis, dll.) Yang akan memberi Anda berdua (dan sebagian besar waktu, itu tidak akan memberi Anda apa pun).
Lihat di bawah ini perbandingan kode RAII dan non RAII:
Tentang RAII
Untuk meringkas (setelah komentar dari Ogre Psalm33 ), RAII bergantung pada tiga konsep:
Ini berarti bahwa dalam kode C ++ yang benar, sebagian besar objek tidak akan dibangun dengan
new
, dan akan dideklarasikan pada stack. Dan bagi mereka yang dibangun menggunakannew
, semua akan entah bagaimana dicakup (misalnya terpasang pada smart pointer).Sebagai pengembang, ini memang sangat kuat karena Anda tidak perlu peduli dengan penanganan sumber daya manual (seperti yang dilakukan dalam C, atau untuk beberapa objek di Jawa yang membuat penggunaan intensif
try
/finally
untuk kasus itu) ...Edit (2012-02-12)
Wilhelmtell benar tentang itu: Ada yang luar biasa cara untuk menipu RAII, semuanya mengarah ke proses berhenti mendadak.
Itu adalah cara luar biasa karena kode C ++ tidak dikotori dengan terminate, exit, dll., Atau dalam kasus dengan pengecualian, kami ingin pengecualian yang tidak ditangani untuk menghentikan proses dan inti membuang gambar memorinya sebagaimana adanya, dan tidak setelah pembersihan.
Tetapi kita harus tetap tahu tentang kasus-kasus itu karena, walaupun jarang terjadi, mereka masih bisa terjadi.
(siapa yang memanggil
terminate
atauexit
dalam kode C ++ biasa? ... Saya ingat harus berurusan dengan masalah itu ketika bermain dengan GLUT : Perpustakaan ini sangat berorientasi C, akan secara aktif merancang untuk membuat hal-hal sulit bagi pengembang C ++ seperti tidak peduli tentang stack data yang dialokasikan , atau memiliki keputusan "menarik" tentang tidak pernah kembali dari loop utama mereka ... Saya tidak akan berkomentar tentang itu) .sumber
Anda akan ingin melihat pointer cerdas, seperti pointer pintar boost .
Dari pada
boost :: shared_ptr akan secara otomatis menghapus begitu jumlah referensi nol:
Perhatikan catatan terakhir saya, "ketika jumlah referensi nol, yang merupakan bagian paling keren. Jadi Jika Anda memiliki banyak pengguna objek Anda, Anda tidak perlu melacak apakah objek masih digunakan. Setelah tidak ada yang merujuk ke Anda pointer bersama, itu hancur.
Namun ini bukan obat mujarab. Meskipun Anda dapat mengakses pointer basis, Anda tidak ingin meneruskannya ke API pihak ketiga kecuali Anda yakin dengan apa yang dilakukannya. Banyak kali, "posting" Anda barang ke thread lain untuk pekerjaan yang harus dilakukan SETELAH lingkup pembuatan selesai. Ini umum dengan PostThreadMessage di Win32:
Seperti biasa, gunakan topi berpikir Anda dengan alat apa pun ...
sumber
Baca di RAII dan pastikan Anda memahaminya.
sumber
Sebagian besar kebocoran memori adalah akibat dari tidak jelasnya kepemilikan benda dan masa pakai.
Hal pertama yang harus dilakukan adalah mengalokasikan pada Stack kapan pun Anda bisa. Ini berkaitan dengan sebagian besar kasus di mana Anda perlu mengalokasikan satu objek untuk beberapa tujuan.
Jika Anda memang perlu 'baru' objek maka sebagian besar waktu itu akan memiliki pemilik yang jelas selama sisa hidupnya. Untuk situasi ini saya cenderung menggunakan banyak koleksi template yang dirancang untuk 'memiliki' objek yang disimpan di dalamnya oleh pointer. Mereka diimplementasikan dengan vektor STL dan wadah peta tetapi memiliki beberapa perbedaan:
Beaf saya dengan STL adalah bahwa ia sangat fokus pada objek Nilai sementara di sebagian besar aplikasi objek adalah entitas unik yang tidak memiliki salinan semantik bermakna yang diperlukan untuk digunakan dalam wadah tersebut.
sumber
Bah, Anda anak-anak muda dan pengumpul sampah baru Anda ...
Aturan yang sangat kuat tentang "kepemilikan" - objek atau bagian apa dari perangkat lunak memiliki hak untuk menghapus objek. Hapus komentar dan nama variabel yang bijak untuk membuatnya jelas jika pointer "memiliki" atau "lihat saja, jangan sentuh". Untuk membantu memutuskan siapa yang memiliki apa, ikuti sebanyak mungkin pola "sandwich" dalam setiap subrutin atau metode.
Terkadang perlu untuk membuat dan menghancurkan di tempat yang sangat berbeda; Saya pikir sulit untuk menghindarinya.
Dalam program apa pun yang membutuhkan struktur data yang kompleks, saya membuat pohon objek ketat yang berisi objek lain - menggunakan pointer "pemilik". Pohon ini memodelkan hierarki dasar konsep domain aplikasi. Contoh adegan 3D memiliki objek, lampu, tekstur. Pada akhir rendering ketika program berhenti, ada cara yang jelas untuk menghancurkan semuanya.
Banyak petunjuk lain didefinisikan sebagaimana diperlukan setiap kali satu entitas perlu mengakses entitas lain, untuk memindai lebih dari satu array atau apa pun; ini adalah "hanya mencari". Sebagai contoh adegan 3D - objek menggunakan tekstur tetapi tidak memiliki; objek lain mungkin menggunakan tekstur yang sama. Penghancuran suatu objek tidak meminta penghancuran tekstur apa pun.
Ya itu memakan waktu tapi itu yang saya lakukan. Saya jarang mengalami kebocoran memori atau masalah lain. Tetapi kemudian saya bekerja di arena terbatas perangkat lunak ilmiah, akuisisi data, dan grafis yang berkinerja tinggi. Saya tidak sering bertransaksi seperti di perbankan dan e-commerce, GUI yang didorong oleh peristiwa atau kekacauan asinkron yang berjejaring tinggi. Mungkin cara-cara baru ketinggalan jaman memiliki keuntungan di sana!
sumber
Pertanyaan bagus!
jika Anda menggunakan c ++ dan Anda mengembangkan aplikasi CPU-and-memory boud real-time (seperti game), Anda perlu menulis Memory Manager Anda sendiri.
Saya pikir yang lebih baik yang dapat Anda lakukan adalah menggabungkan beberapa karya menarik dari berbagai penulis, saya dapat memberi Anda beberapa petunjuk:
Alokasi ukuran tetap banyak dibahas, di mana-mana di internet
Alokasi Objek Kecil diperkenalkan oleh Alexandrescu pada tahun 2001 dalam bukunya yang sempurna "Desain c ++ modern"
Sebuah kemajuan besar (dengan kode sumber didistribusikan) dapat ditemukan dalam artikel yang luar biasa di Game Programming Gem 7 (2008) bernama "Pengalokasi Heap Kinerja Tinggi" yang ditulis oleh Dimitar Lazarov
Daftar sumber daya yang bagus dapat ditemukan di artikel ini
Jangan mulai menulis pengalokasi noob yang tidak berguna oleh Anda sendiri ... DOKUMEN DIRI SENDIRI terlebih dahulu.
sumber
Salah satu teknik yang telah menjadi populer dengan manajemen memori di C ++ adalah RAII . Pada dasarnya Anda menggunakan konstruktor / destruktor untuk menangani alokasi sumber daya. Tentu saja ada beberapa detail menjengkelkan lainnya di C ++ karena keamanan pengecualian, tetapi ide dasarnya cukup sederhana.
Masalahnya biasanya menjadi salah satu kepemilikan. Saya sangat merekomendasikan membaca seri C ++ Efektif oleh Scott Meyers dan Desain C ++ Modern oleh Andrei Alexandrescu.
sumber
Sudah ada banyak cara untuk tidak bocor, tetapi jika Anda membutuhkan alat untuk membantu melacak kebocoran, lihat:
sumber
Pointer cerdas pengguna di mana pun Anda bisa! Seluruh kelas kebocoran memori hilang begitu saja.
sumber
Bagikan dan ketahui aturan kepemilikan memori di seluruh proyek Anda. Menggunakan aturan COM membuat konsistensi terbaik (parameter yang dimiliki oleh pemanggil, callee harus menyalin; [keluar] params dimiliki oleh pemanggil, callee harus membuat salinan jika menyimpan referensi; dll)
sumber
valgrind adalah alat yang baik untuk memeriksa kebocoran memori program Anda saat runtime juga.
Ini tersedia di sebagian besar rasa Linux (termasuk Android) dan di Darwin.
Jika Anda terbiasa menulis unit test untuk program Anda, Anda harus terbiasa menjalankan tes secara sistematis. Ini berpotensi menghindari banyak kebocoran memori pada tahap awal. Biasanya juga lebih mudah untuk menunjukkannya dalam tes sederhana yang ada dalam perangkat lunak lengkap.
Tentu saja saran ini tetap berlaku untuk alat pemeriksaan memori lainnya.
sumber
Juga, jangan gunakan memori yang dialokasikan secara manual jika ada kelas perpustakaan std (misalnya vektor). Pastikan jika Anda melanggar aturan itu bahwa Anda memiliki destruktor virtual.
sumber
Jika Anda tidak bisa / tidak menggunakan smart pointer untuk sesuatu (walaupun itu seharusnya merupakan tanda merah besar), ketikkan kode Anda dengan:
Itu sudah jelas, tetapi pastikan Anda mengetiknya sebelum mengetikkan kode apa pun di dalam ruang lingkup
sumber
Sumber yang sering muncul dari bug ini adalah ketika Anda memiliki metode yang menerima referensi atau penunjuk ke suatu objek tetapi membiarkan kepemilikan tidak jelas. Konvensi gaya dan komentar dapat membuat hal ini lebih kecil kemungkinannya.
Biarkan case di mana fungsi mengambil kepemilikan objek menjadi case khusus. Dalam semua situasi di mana ini terjadi, pastikan untuk menulis komentar di sebelah fungsi di file header yang menunjukkan ini. Anda harus berusaha keras untuk memastikan bahwa dalam sebagian besar kasus, modul atau kelas yang mengalokasikan sebuah objek juga bertanggung jawab untuk melakukan deallokasi.
Menggunakan const dapat banyak membantu dalam beberapa kasus. Jika suatu fungsi tidak akan memodifikasi objek, dan tidak menyimpan referensi untuk itu yang tetap setelah itu kembali, terima referensi const. Dari membaca kode penelepon, akan jelas bahwa fungsi Anda belum menerima kepemilikan objek. Anda bisa memiliki fungsi yang sama menerima pointer non-const, dan penelepon mungkin atau mungkin tidak berasumsi bahwa callee menerima kepemilikan, tetapi dengan referensi const tidak ada pertanyaan.
Jangan gunakan referensi non-const dalam daftar argumen. Sangat tidak jelas ketika membaca kode penelepon bahwa callee mungkin telah menyimpan referensi ke parameter.
Saya tidak setuju dengan komentar yang merekomendasikan referensi terhitung. Ini biasanya berfungsi dengan baik, tetapi ketika Anda memiliki bug dan tidak berfungsi, terutama jika destruktor Anda melakukan sesuatu yang tidak sepele, seperti dalam program multithreaded. Cobalah menyesuaikan desain Anda agar tidak perlu penghitungan referensi jika tidak terlalu sulit.
sumber
Kiat-kiat agar Penting:
-Tip # 1 Selalu ingat untuk mendeklarasikan destruktor Anda "virtual".
-Tip # 2 Gunakan RAII
-Tip # 3 Gunakan smartpointer boost
-Tip # 4 Jangan menulis Smartpointers buggy Anda sendiri, gunakan boost (pada proyek yang sedang saya jalani sekarang saya tidak bisa menggunakan boost, dan saya sudah menderita harus men-debug pointer pintar saya sendiri, saya pasti tidak akan mengambil rute yang sama lagi, tapi sekali lagi saat ini saya tidak dapat menambahkan peningkatan pada dependensi kami)
-Tip # 5 Jika beberapa kasual / non-kinerja kritis (seperti dalam game dengan ribuan objek) bekerja lihat wadah penunjuk penambah Thorsten Ottosen
-Tip # 6 Temukan tajuk deteksi kebocoran untuk platform pilihan Anda seperti tajuk "vld" Visual Leak Detection
sumber
Jika Anda bisa, gunakan boost shared_ptr dan standar C ++ auto_ptr. Mereka menyampaikan semantik kepemilikan.
Ketika Anda mengembalikan auto_ptr, Anda memberi tahu penelepon bahwa Anda memberi mereka memori.
Ketika Anda mengembalikan shared_ptr, Anda memberi tahu penelepon bahwa Anda memiliki referensi untuk itu dan mereka mengambil bagian dari kepemilikan, tetapi itu bukan semata-mata tanggung jawab mereka.
Semantik ini juga berlaku untuk parameter. Jika pemanggil memberikan Anda auto_ptr, mereka memberi Anda kepemilikan.
sumber
Yang lain menyebutkan cara menghindari kebocoran memori di tempat pertama (seperti smart pointer). Tetapi alat profiling dan analisis memori sering kali merupakan satu-satunya cara untuk melacak masalah memori begitu Anda memilikinya.
Valgrind memcheck adalah yang gratis dan sangat bagus.
sumber
Hanya untuk MSVC, tambahkan berikut ini di atas setiap file .cpp:
Kemudian, ketika debugging dengan VS2003 atau lebih tinggi, Anda akan diberitahu tentang kebocoran ketika program Anda keluar (itu trek baru / hapus). Ini dasar, tetapi itu telah membantu saya di masa lalu.
sumber
valgrind (hanya tersedia untuk platform * nix) adalah pemeriksa memori yang sangat bagus
sumber
Jika Anda akan mengelola memori secara manual, Anda memiliki dua kasing:
Jika Anda perlu melanggar salah satu dari aturan ini, harap dokumentasikan.
Ini semua tentang kepemilikan pointer.
sumber
Sebenarnya itu juga mekanisme yang digunakan oleh "smart pointer" dan disebut sebagai RAII oleh beberapa penulis lain ;-).
Dengan cara ini Anda dapat membuat hasil debug (alamat mana yang dialokasikan dan tidak dialokasikan, ...) dengan mudah, jika diperlukan.
sumber
Anda dapat mencegat fungsi alokasi memori dan melihat apakah ada beberapa zona memori yang tidak dibebaskan saat keluar dari program (meskipun tidak cocok untuk semua aplikasi).
Ini juga dapat dilakukan pada waktu kompilasi dengan mengganti operator baru dan menghapus dan fungsi alokasi memori lainnya.
Sebagai contoh, periksa di situs ini [Alokasi memori debug di C ++] Catatan: Ada juga trik untuk menghapus operator seperti ini:
Anda dapat menyimpan dalam beberapa variabel nama file dan ketika operator hapus yang kelebihan beban akan tahu dari mana tempat itu berasal. Dengan cara ini Anda dapat memiliki jejak setiap delete dan malloc dari program Anda. Di akhir urutan pemeriksaan memori, Anda harus dapat melaporkan blok memori yang dialokasikan tidak 'dihapus' dengan mengidentifikasikannya dengan nama file dan nomor baris yang saya kira yang Anda inginkan.
Anda juga dapat mencoba sesuatu seperti BoundsChecker di bawah Visual Studio yang cukup menarik dan mudah digunakan.
sumber
Kami membungkus semua fungsi alokasi kami dengan layer yang menambahkan string singkat di depan dan bendera penjaga di akhir. Jadi misalnya Anda akan memiliki panggilan ke "myalloc (pszSomeString, iSize, iAlignment); atau baru (" description ", iSize) MyObject (); yang secara internal mengalokasikan ukuran yang ditentukan ditambah ruang yang cukup untuk header dan sentinel Anda. Tentu saja , jangan lupa untuk mengomentari ini untuk pembuatan non-debug! Dibutuhkan sedikit lebih banyak memori untuk melakukan ini tetapi manfaatnya jauh lebih besar daripada biayanya.
Ini memiliki tiga manfaat - pertama memungkinkan Anda untuk dengan mudah dan cepat melacak kode apa yang bocor, dengan melakukan pencarian cepat untuk kode yang dialokasikan di 'zona' tertentu tetapi tidak dibersihkan ketika zona tersebut seharusnya dibebaskan. Dapat juga berguna untuk mendeteksi ketika batas telah ditimpa dengan memeriksa untuk memastikan semua penjaga tetap utuh. Ini telah menyelamatkan kita berkali-kali ketika mencoba menemukan crash atau kesalahan susunan array yang tersembunyi dengan baik. Manfaat ketiga adalah melacak penggunaan memori untuk melihat siapa pemain besar - kumpulan deskripsi tertentu dalam MemDump memberi tahu Anda ketika 'suara' mengambil ruang lebih banyak dari yang Anda perkirakan, misalnya.
sumber
C ++ dirancang RAII dalam pikiran. Sebenarnya tidak ada cara yang lebih baik untuk mengelola memori di C ++ saya pikir. Tapi hati-hati untuk tidak mengalokasikan potongan yang sangat besar (seperti objek penyangga) pada cakupan lokal. Hal ini dapat menyebabkan tumpukan meluap dan, jika ada cacat dalam batas memeriksa saat menggunakan potongan itu, Anda dapat menimpa variabel lain atau mengembalikan alamat, yang mengarah ke semua jenis lubang keamanan.
sumber
Salah satu satu-satunya contoh tentang mengalokasikan dan menghancurkan di tempat yang berbeda adalah pembuatan utas (parameter yang Anda lewati). Tetapi bahkan dalam hal ini mudah. Berikut adalah fungsi / metode membuat utas:
Di sini alih-alih fungsi utas
Easyn cantik bukan? Dalam hal pembuatan utas gagal, sumber daya akan dibebaskan (dihapus) oleh auto_ptr, jika tidak, kepemilikan akan diteruskan ke utas. Bagaimana jika utasnya sangat cepat sehingga setelah dibuat ia melepaskan sumber sebelum
dipanggil dalam fungsi / metode utama? Tidak ada! Karena kita akan 'memberi tahu' auto_ptr untuk mengabaikan deallokasi. Apakah manajemen memori C ++ mudah, bukan? Bersulang,
Ema!
sumber
Kelola memori dengan cara yang sama seperti Anda mengelola sumber daya lainnya (pegangan, file, koneksi db, soket ...). GC juga tidak akan membantu Anda.
sumber
Satu pengembalian tepat dari fungsi apa pun. Dengan begitu Anda dapat melakukan deallokasi di sana dan tidak pernah melewatkannya.
Sebaliknya, terlalu mudah untuk membuat kesalahan:
sumber