Pedoman umum untuk menghindari kebocoran memori di C ++ [ditutup]

130

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?

dulipishi
sumber
26
Tampak cukup konstruktif bagi saya.
Shoerob
11
Ini konstruktif. Dan jawabannya didukung oleh fakta, keahlian, referensi, dll. Dan lihat jumlah upvotes / jawaban .. !!
Samitha Chathuranga

Jawaban:

40

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 .

Andri Möll
sumber
1
Untuk mengkompilasi menggunakan g ++ seseorang perlu menambahkan param: -std = c ++ 0x
Paweł Szczur
atau Anda dapat mengkompilasi dengan g ++ menggunakan nilai flag -std = c ++ 11
Prabhash Rathore
200

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

Object* x = new Object;

atau bahkan

shared_ptr<Object> x(new Object);

ketika Anda bisa menulis

Object x;
Ross Smith
sumber
34
Saya berharap saya bisa memberikan ini +10. Ini adalah masalah terbesar yang saya lihat dengan kebanyakan programmer C ++ saat ini, dan saya berasumsi itu karena mereka mempelajari Java sebelum C ++.
Kristopher Johnson
Poin yang sangat menarik - saya bertanya-tanya mengapa saya memiliki masalah manajemen memori C ++ jauh lebih jarang daripada dalam bahasa lain, tapi sekarang saya mengerti mengapa: sebenarnya memungkinkan hal-hal untuk ditumpuk seperti di vanilla C.
ArtOfWarfare
Jadi apa yang Anda lakukan jika Anda menulis Object x; dan kemudian ingin membuang x? katakanlah x dibuat dalam metode utama.
Yamcha
3
@ user1316459 C ++ memungkinkan Anda untuk membuat cakupan dengan cepat juga. Yang harus Anda lakukan adalah membungkus seumur hidup x dengan kawat gigi seperti: {Object x; x.Melakukan sesuatu; }. Setelah '}' final, destruktor x akan disebut membebaskan semua sumber daya yang dikandungnya. Jika x, itu sendiri, adalah memori yang akan dialokasikan pada heap, saya sarankan membungkusnya dalam unique_ptr sehingga itu dibersihkan dengan mudah dan tepat.
David Peterson
1
Robert: ya. Ross tidak mengatakan "Jangan pernah menulis [kode yang mengandung baru]", katanya "Jangan menulis [itu] ketika Anda bisa [meletakkannya di tumpukan]". Objek besar di heap akan terus menjadi panggilan yang tepat di sebagian besar situasi, terutama untuk kode intensif kinerja.
codetaku
104

Gunakan RAII

  • Lupakan Pengumpulan Sampah (Gunakan RAII). Perhatikan bahwa Pengumpul Sampah bahkan dapat bocor juga (jika Anda lupa untuk "membatalkan" beberapa referensi di Java / C #), dan Pengumpul Sampah tidak akan membantu Anda membuang sumber daya (jika Anda memiliki objek yang memperoleh pegangan untuk file, file tidak akan dibebaskan secara otomatis ketika objek akan keluar dari ruang lingkup jika Anda tidak melakukannya secara manual di Jawa, atau menggunakan pola "buang" di C #).
  • Lupakan aturan "satu pengembalian per fungsi" . Ini adalah saran C yang baik untuk menghindari kebocoran, tetapi sudah usang dalam C ++ karena penggunaan pengecualian (gunakan RAII sebagai gantinya).
  • Dan sementara "Pola Sandwich" adalah saran C yang baik, itu sudah usang dalam C ++ karena penggunaan pengecualian (gunakan RAII sebagai gantinya).

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:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

Tentang RAII

Untuk meringkas (setelah komentar dari Ogre Psalm33 ), RAII bergantung pada tiga konsep:

  • Setelah objek dibangun, itu hanya berfungsi! Dapatkan sumber daya di konstruktor.
  • Penghancuran objek sudah cukup! Apakah sumber daya gratis di destructor.
  • Ini semua tentang ruang lingkup! Objek yang dilingkupi (lihat contoh doRAIIStatic di atas) akan dibangun pada deklarasi mereka, dan akan dihancurkan saat eksekusi keluar dari ruang lingkup, tidak peduli bagaimana jalan keluarnya (return, break, exception, dll.).

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 menggunakan new, 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 / finallyuntuk kasus itu) ...

Edit (2012-02-12)

"Benda yang dicakup ... akan dihancurkan ... tidak peduli jalan keluar" itu tidak sepenuhnya benar. ada cara untuk menipu RAII. setiap rasa mengakhiri () akan memotong pembersihan. exit (EXIT_SUCCESS) adalah sebuah oxymoron dalam hal ini.

- wilhelmtell

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 terminateatau exitdalam 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) .

paercebal
sumber
Haruskah kelas T menggunakan RAII untuk memastikan bahwa doRAIIStatic () tidak membocorkan memori? Misalnya T p (); p.doSandwich (); Saya tidak benar-benar tahu banyak tentang ini.
Daniel O
@ Ogre Psalm33: Terima kasih atas komentarnya. Tentu saja kamu benar. Saya menambahkan kedua tautan ke halaman Wikipedia RAII, dan ringkasan kecil tentang apa itu RAII.
paercebal
1
@Shiftbit: Tiga cara, sesuai urutan pilihan: _ _ _ 1. Letakkan objek nyata di dalam wadah STL. _ _ _ 2. Masukkan smart pointer (shared_ptr) objek di dalam wadah STL. _ _ _ 3. Masukkan pointer mentah ke dalam wadah STL, tetapi bungkus wadah untuk mengontrol akses ke data. Wrapper akan memastikan destructor akan membebaskan objek yang dialokasikan, dan accessor wrapper akan memastikan tidak ada yang rusak saat mengakses / memodifikasi wadah.
paercebal
1
@ Robert: Di C ++ 03, Anda akan menggunakan doRAIIDynamic dalam fungsi yang harus memberikan kepemilikan pada fungsi anak atau orang tua (atau lingkup global). Atau ketika Anda menerima antarmuka ke objek polimorf melalui pabrik (mengembalikan pointer cerdas, jika ditulis dengan benar). Dalam C ++ 11, ini kurang terjadi karena Anda dapat membuat objek Anda bergerak, sehingga memberikan kepemilikan objek yang dinyatakan pada stack lebih mudah ...
paercebal
2
@ Robert: ... Perhatikan bahwa mendeklarasikan objek pada stack tidak berarti objek tersebut tidak menggunakan heap secara internal (perhatikan negasi ganda ... :-) ...). Sebagai contoh, std :: string yang diimplementasikan dengan Small String Optimization akan memiliki buffer "di tumpukan kelas" untuk string kecil (~ 15 karakter), dan akan menggunakan pointer ke memori di heap untuk string yang lebih besar ... Tetapi dari luar, std :: string masih merupakan tipe nilai yang Anda nyatakan (biasanya) pada stack dan Anda gunakan karena Anda akan menggunakan integer (sebagai lawan dari: karena Anda akan menggunakan antarmuka untuk kelas polymorph).
paercebal
25

Anda akan ingin melihat pointer cerdas, seperti pointer pintar boost .

Dari pada

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr akan secara otomatis menghapus begitu jumlah referensi nol:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

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:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Seperti biasa, gunakan topi berpikir Anda dengan alat apa pun ...

Doug T.
sumber
12

Baca di RAII dan pastikan Anda memahaminya.

Gulungan
sumber
11

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:

  • Koleksi-koleksi ini tidak dapat disalin atau ditugaskan. (Begitu mereka berisi objek.)
  • Pointer ke objek dimasukkan ke dalamnya.
  • Ketika koleksi dihapus, destruktor pertama kali dipanggil pada semua objek dalam koleksi. (Saya memiliki versi lain di mana ia menegaskan jika dihancurkan dan tidak kosong.)
  • Karena menyimpan pointer, Anda juga dapat menyimpan objek yang diwarisi dalam wadah ini.

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.

Jeroen Dirks
sumber
10

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.

create a thing
use that thing
destroy that thing

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!

DarW
sumber
Saya sepenuhnya setuju. Bekerja di lingkungan yang tertanam Anda mungkin juga tidak memiliki kemewahan perpustakaan pihak ketiga.
simon
6
Saya tidak setuju. di bagian "gunakan benda itu", jika pengembalian atau pengecualian dilemparkan, maka Anda akan kehilangan alokasi. Adapun kinerja, std :: auto_ptr akan dikenakan biaya apa pun. Bukannya saya tidak pernah kode dengan cara yang sama Anda lakukan. Hanya saja ada perbedaan antara kode aman 100% dan 99%. :-)
paercebal
8

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.

ugasoft
sumber
5

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.

Jason Dagit
sumber
4

Pointer cerdas pengguna di mana pun Anda bisa! Seluruh kelas kebocoran memori hilang begitu saja.

DougN
sumber
4

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)

Seth Morris
sumber
4

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.

kriss
sumber
3

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.

Joseph
sumber
2

Jika Anda tidak bisa / tidak menggunakan smart pointer untuk sesuatu (walaupun itu seharusnya merupakan tanda merah besar), ketikkan kode Anda dengan:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

Itu sudah jelas, tetapi pastikan Anda mengetiknya sebelum mengetikkan kode apa pun di dalam ruang lingkup

Seth Morris
sumber
2

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.

Jonathan
sumber
2

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

Robert Gould
sumber
Saya mungkin melewatkan satu trik, tetapi bagaimana bisa 'permainan' dan 'non-kinerja-kritis' berada dalam kalimat yang sama?
Adam Naylor
Game tentu saja merupakan contoh skenario kritis. Mungkin telah gagal menjadi jelas di sana
Robert Gould
Kiat # 1 seharusnya hanya diterapkan jika kelas memiliki setidaknya satu metode virtual. Saya tidak akan pernah memaksakan destruktor virtual yang tidak berguna pada kelas yang tidak dimaksudkan untuk berfungsi sebagai kelas dasar dalam pohon warisan polimorfik.
antred
1

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.

Justin Rudd
sumber
1

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.

eli
sumber
1

Hanya untuk MSVC, tambahkan berikut ini di atas setiap file .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

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.

rampok
sumber
1

valgrind (hanya tersedia untuk platform * nix) adalah pemeriksa memori yang sangat bagus

Ronny Brendel
sumber
1

Jika Anda akan mengelola memori secara manual, Anda memiliki dua kasing:

  1. Saya membuat objek (mungkin secara tidak langsung, dengan memanggil fungsi yang mengalokasikan objek baru), saya menggunakannya (atau fungsi yang saya panggil menggunakannya), lalu saya membebaskannya.
  2. Seseorang memberi saya referensi, jadi saya tidak boleh membebaskannya.

Jika Anda perlu melanggar salah satu dari aturan ini, harap dokumentasikan.

Ini semua tentang kepemilikan pointer.

Null303
sumber
1
  • Cobalah untuk menghindari mengalokasikan objek secara dinamis. Selama kelas memiliki konstruktor dan destruktor yang sesuai, gunakan variabel dari tipe kelas, bukan pointer ke sana, dan Anda menghindari alokasi dinamis dan deallokasi karena kompiler akan melakukannya untuk Anda.
    Sebenarnya itu juga mekanisme yang digunakan oleh "smart pointer" dan disebut sebagai RAII oleh beberapa penulis lain ;-).
  • Saat Anda meneruskan objek ke fungsi lain, lebih suka parameter referensi daripada pointer. Ini menghindari beberapa kemungkinan kesalahan.
  • Deklarasikan parameter const, jika memungkinkan, terutama pointer ke objek. Dengan begitu objek tidak dapat dibebaskan "secara tidak sengaja" (kecuali jika Anda membuang const ;-))).
  • Minimalkan jumlah tempat dalam program tempat Anda melakukan alokasi dan deallokasi memori. E. g. jika Anda mengalokasikan atau membebaskan jenis yang sama beberapa kali, tulis fungsi untuknya (atau metode pabrik ;-)).
    Dengan cara ini Anda dapat membuat hasil debug (alamat mana yang dialokasikan dan tidak dialokasikan, ...) dengan mudah, jika diperlukan.
  • Gunakan fungsi pabrik untuk mengalokasikan objek dari beberapa kelas terkait dari satu fungsi.
  • Jika kelas Anda memiliki kelas dasar umum dengan destruktor virtual, Anda dapat membebaskan semuanya menggunakan fungsi yang sama (atau metode statis).
  • Periksa program Anda dengan alat-alat seperti purify (sayangnya banyak $ / € / ...).
mh
sumber
0

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:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

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.

INS
sumber
0

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.

screenglow
sumber
0

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.

artificialidiot
sumber
0

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:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Di sini alih-alih fungsi utas

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

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

param.release();

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!

Emanuele Oriani
sumber
0

Kelola memori dengan cara yang sama seperti Anda mengelola sumber daya lainnya (pegangan, file, koneksi db, soket ...). GC juga tidak akan membantu Anda.

Nemanja Trifunovic
sumber
-3

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:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.
Seth Morris
sumber
Jawaban Anda tidak cocok dengan kode contoh di sini? Saya setuju dengan jawaban "hanya satu pengembalian" tetapi kode contoh menunjukkan apa yang TIDAK boleh dilakukan.
simon
1
C ++ Poin RAII adalah tepatnya untuk menghindari jenis kode yang Anda tulis. Dalam C, ini mungkin hal yang benar untuk dilakukan. Namun dalam C ++, kode Anda cacat. Sebagai contoh: Bagaimana jika b () baru melempar? Anda bocor a.
paercebal