Stack, Static, dan Heap dalam C ++

Saya mencari, tetapi saya tidak memahami ketiga hal ini dengan sangat baik. Kapan saya perlu menggunakan alokasi dinamis (dalam tumpukan) dan apa keuntungan sebenarnya? Apa masalah statika dan stack? Bisakah saya menulis seluruh aplikasi tanpa mengalokasikan variabel di heap?

Saya mendengar bahwa dalam bahasa lain ada "pengumpul sampah", jadi Anda tidak perlu khawatir tentang memori. Apa yang dilakukan oleh pemulung?

Apa yang Anda bisa memanipulasi memori sendiri agar tidak menggunakan pengumpul sampah ini?

Segera setelah seseorang memberi tahu saya dengan ungkapan ini:

 int * asafe=new int; 

Saya memiliki pointer ke pointer. Apa artinya ini? Itu berbeda:

 asafe=new int; 

?

132
03 янв. Hai ditetapkan pada 03 Januari 2009-01-03 08:41 '09 pada 08:41 2009-01-03 08:41
@ 9 jawaban

Pertanyaan serupa diajukan , tetapi dia tidak bertanya tentang statis.

Ringkasan dari apa memori statis, tumpukan dan tumpukan:

  • Variabel statis pada dasarnya adalah variabel global, bahkan jika Anda tidak dapat mengaksesnya di seluruh dunia. Biasanya baginya ada alamat yang ada di file executable itu sendiri. Untuk keseluruhan program, hanya ada satu salinan. Tidak peduli berapa kali Anda memasukkan panggilan fungsi (atau kelas) (dan berapa utas!), Variabel ini milik lokasi memori yang sama.

  • Tumpukan adalah tumpukan memori yang dapat digunakan secara dinamis. Jika Anda menginginkan 4kb untuk suatu objek, maka distributor dinamis akan melihat daftar ruang kosong di heap, pilih potongan 4kb dan transfer ke Anda. Sebagai aturan, pengalokasi memori dinamis (malloc, baru, dll.) Berjalan di akhir memori dan bekerja di arah yang berlawanan.

  • Menjelaskan bagaimana tumpukan tumbuh dan menyusut sedikit di luar cakupan jawaban ini, tetapi cukuplah untuk mengatakan bahwa Anda selalu menambah dan menghapus hanya dari akhir. Tumpukan biasanya mulai tinggi dan tumbuh ke alamat yang lebih rendah. Anda kehabisan memori ketika tumpukan menemukan pengalokasi dinamis di suatu tempat di tengah (tetapi mengacu pada memori fisik dan virtual dan fragmentasi). Beberapa utas akan membutuhkan banyak tumpukan (biasanya proses ini memiliki ukuran minimum untuk tumpukan).

Jika Anda ingin menggunakan masing-masing:

  • Statika / global berguna untuk memori, yang, seperti yang Anda tahu, Anda akan selalu butuhkan, dan Anda tahu bahwa Anda tidak pernah ingin membebaskan Anda. (Omong-omong, lingkungan tertanam dapat dianggap hanya memiliki memori statis ... tumpukan dan tumpukan adalah bagian dari ruang alamat yang diketahui digunakan bersama oleh jenis memori ketiga: kode program. Program sering melakukan alokasi dinamis dari memori statis mereka ketika mereka memerlukan hal-hal seperti daftar yang ditautkan Tetapi terlepas dari apakah memori statis itu sendiri (buffer) itu sendiri tidak "didistribusikan", dan objek lain dialokasikan dari memori yang disimpan dalam buffer untuk tujuan ini.Tidak tertanam, dan konsol game sering menghindari dinamika yang tertanam. mekanisme memori yang mendukung kontrol ketat dari proses distribusi, menggunakan buffer ukuran yang diberikan untuk semua distribusi.)

  • Variabel stack berguna ketika Anda tahu bahwa selama fungsinya berada di suatu area (di suatu tempat di stack), Anda akan membutuhkan variabel. Tumpukan baik untuk variabel yang Anda butuhkan untuk kode, di mana mereka berada, tetapi tidak diperlukan di luar kode itu. Mereka juga sangat menyenangkan ketika Anda mengakses sumber daya, seperti file, dan Anda ingin sumber daya secara otomatis pergi ketika Anda meninggalkan kode ini.

  • Alokasi tumpukan (memori yang dialokasikan secara dinamis) berguna jika Anda ingin lebih fleksibel daripada lebih tinggi. Seringkali fungsi dipanggil untuk merespons suatu peristiwa (pengguna mengklik tombol "Buat Jendela"). Untuk jawaban yang benar, mungkin perlu mengalokasikan objek baru (objek Box baru), yang harus berdiri lama setelah fungsi dilepaskan, sehingga tidak bisa berada di tumpukan. Tetapi Anda tidak tahu berapa banyak kotak yang Anda butuhkan di awal program, sehingga tidak bisa statis.

Pengumpulan sampah

Baru-baru ini, saya mendengar banyak tentang betapa hebatnya pengumpul sampah, jadi mungkin sedikit suara khusus akan berguna.

Pengumpulan sampah adalah mekanisme yang sangat baik ketika kinerja bukan masalah besar. Saya mendengar bahwa GC semakin baik dan sulit, tetapi faktanya Anda mungkin dipaksa untuk menerima penalti untuk eksekusi (tergantung pada use case). Dan jika Anda malas, mungkin masih berfungsi salah. Pada saat terbaik, pengumpul sampah memahami bahwa ingatan Anda hi>referensi ). Tetapi jika Anda memiliki objek yang merujuk pada dirinya sendiri (mungkin merujuk pada objek lain yang kembali), maka penghitungan referensi tidak dengan sendirinya menunjukkan bahwa memori dapat dihapus. Dalam hal ini, GC harus melihat seluruh sup referensi dan mencari tahu apakah ada pulau yang disebutkan sendiri. Offline, saya akan menyarankan itu untuk operasi O (n ^ 2), tetapi apa pun itu, itu bisa menjadi lebih buruk jika Anda tertarik pada kinerja. (Sunting: Martin B menunjukkan bahwa itu O (n) untuk algoritma yang cukup efisien. Masih O (n) terlalu banyak jika Anda tertarik pada kinerja dan bisa mendapatkan gratis untuk waktu yang tidak terbatas tanpa pengumpulan sampah.)

Secara pribadi, ketika saya mendengar bahwa orang-orang mengatakan bahwa C ++ tidak memiliki pengumpulan sampah, tag pikiran saya adalah fungsi dari C ++, tetapi saya mungkin adalah minoritas. Mungkin hal yang paling sulit bagi orang untuk belajar tentang pemrograman dalam C dan C ++ adalah pointer dan penanganan yang benar dari alokasi memori dinamis mereka. Beberapa bahasa lain, seperti Python, akan mengerikan tanpa GC, jadi saya pikir itu turun ke apa yang Anda inginkan dari bahasa tersebut. Jika Anda membutuhkan kinerja yang dapat diandalkan, C ++ tanpa pengumpulan sampah adalah satu-satunya hal yang dapat saya pikirkan. Jika Anda ingin kemudahan penggunaan dan roda pembelajaran (untuk menyelamatkan Anda dari tabrakan, tidak mengharuskan Anda mempelajari manajemen memori yang "benar"), pilih sesuatu dengan GC. Bahkan jika Anda tahu cara mengelola memori dengan baik, itu akan menghemat waktu Anda, yang dapat Anda habiskan untuk mengoptimalkan kode lain. Sebenarnya, ini tidak terlalu banyak, tetapi jika Anda benar-benar membutuhkan kinerja yang dapat diandalkan (dan kemampuan untuk mengetahui persis apa yang terjadi ketika di balik selimut), saya akan tetap menggunakan C ++. Ada alasan mengapa setiap mesin gim utama yang pernah saya dengar ada di C ++ (jika bukan C atau assembly). Python dan yang lainnya bagus untuk skrip, tetapi tidak untuk mesin gim utama.

187
03 янв. jawabannya diberikan oleh pasar 03 Jan. 2009-01-03 17:08 '09 pada 17:08 2009-01-03 17:08

Tentu saja, semuanya tidak terlalu akurat. Ambillah dengan garam ketika Anda membacanya :)

Nah, tiga hal yang Anda rujuk adalah waktu penyimpanan otomatis, statis, dan dinamis yang ada hubungannya dengan berapa lama benda itu hidup dan kapan mereka mulai hidup.


Waktu penyimpanan otomatis

Anda menggunakan waktu penyimpanan otomatis untuk data pendek dan kecil , yang hanya diperlukan secara lokal :

 if(some condition) { int a[3]; // array a has automatic storage duration fill_it(a); print_it(a); } 

Umur berakhir segera setelah kami keluar dari blok, dan dimulai segera setelah objek ditentukan. Mereka adalah bentuk penyimpanan paling sederhana dan lebih cepat daripada penyimpanan dinamis.


Waktu penyimpanan statis

Anda menggunakan waktu penyimpanan statis untuk variabel bebas, di mana kode apa pun dapat tersedia sepanjang waktu jika area mereka memungkinkan untuk penggunaan tersebut (area namespace) dan untuk variabel lokal yang perlu memperpanjang masa pakai ketika mereka meninggalkan area mereka (area lokal). ) dan variabel anggota, yang harus dipisahkan oleh semua objek dari kelasnya (kelas lingkup). Umur mereka tergantung pada volume di mana mereka berada. Mereka dapat memiliki area namespace dan area lokal dan area kelas . Apa yang benar dari kedua hal ini adalah ketika kehidupan dimulai, kehidupan berakhir pada akhir program . Berikut ini dua contoh:

 // static storage duration. in global namespace scope string globalA; int main() { foo(); foo(); } void foo() { // static storage duration. in local scope static string localA; localA += "ab" cout << localA; } 

Program mencetak ababab , karena localA tidak dihancurkan ketika keluar dari bloknya. Anda dapat mengatakan bahwa objek dengan area lokal mulai hidup ketika kontrol mencapai definisinya . Untuk localA ini terjadi ketika fungsi tubuh dimasukkan. Untuk objek di area namespace, umur dimulai dengan program dimulai . Hal yang sama berlaku untuk objek statis dari kelas lingkup:

 class A { static string classScopeA; }; string A::classScopeA; A a, b;  ==  == > 

Seperti yang Anda lihat, classScopeA tidak terikat ke objek spesifik dari kelasnya, tetapi ke kelas itu sendiri. Alamat ketiga nama yang disebutkan di atas adalah sama, dan semua menunjuk objek yang sama. Ada aturan khusus tentang kapan dan bagaimana objek statis diinisialisasi, tetapi kami tidak akan khawatir tentang hal itu. Ini berarti kegagalan inisialisasi statis.


Waktu penyimpanan yang dinamis

Waktu penyimpanan terakhir adalah dinamis. Anda menggunakannya jika Anda ingin benda hidup di pulau lain, dan Anda ingin meletakkan petunjuk di sekitar tautan ini. Anda juga menggunakannya jika objek Anda besar , dan jika Anda ingin membuat array ukuran yang hanya diketahui saat runtime . Karena fleksibilitas ini, objek dengan durasi penyimpanan dinamis sangat kompleks dan lambat untuk dikelola. Objek dengan durasi dinamis mulai ada ketika panggilan operator baru yang sesuai terjadi:

 int main() { // the object that s points to has dynamic storage // duration string *s = new string; // pass a pointer pointing to the object around. // the object itself isn't touched foo(s); delete s; } void foo(string *s) { cout << s->size(); } 

Umurnya berakhir hanya ketika Anda memanggil delete untuk mereka. Jika Anda lupa ini, benda-benda ini tidak pernah berakhir seumur hidup. Dan objek kelas yang mendefinisikan konstruktor pengguna yang dideklarasikan tidak akan dipanggil oleh destruktor mereka. Objek dengan memori dinamis memerlukan pemrosesan sumber daya secara manual dan sumber daya memori terkait. Ada perpustakaan untuk memfasilitasi penggunaannya. Pengumpulan sampah eksplisit untuk objek tertentu dapat diatur menggunakan smart pointer:

 int main() { shared_ptr<string> s(new string); foo(s); } void foo(shared_ptr<string> s) { cout << s->size(); } 

Anda tidak perlu khawatir tentang memanggil delete: ptr generik melakukan ini untuk Anda jika pointer terakhir yang merujuk ke suatu objek keluar dari cakupan. Total ptr itu sendiri memiliki waktu penyimpanan otomatis. Dengan demikian, masa pakainya dikendalikan secara otomatis, memungkinkannya untuk memeriksa apakah objek dinamis yang ditentukan harus dihapus dalam destruktornya. Untuk bantuan tentang shared_ptr, lihat Dokumen Lanjut: http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm

47
03 янв. Jawaban yang diberikan Johannes Schaub - litb 03 Jan 2009-01-03 09:12 '09 jam 9:12 2009-01-03 09:12

Ini dikatakan dengan hati-hati, sebagai "jawaban singkat":

  • variabel statis (kelas)
    Seumur hidup = waktu pelaksanaan program (1)
    visibilitas = ditentukan oleh pengubah akses (pribadi / dilindungi / publik)

  • variabel statis (ruang lingkup global)
    Seumur hidup = waktu pelaksanaan program (1)
    visibility = unit kompilasi dibuat dalam (2)

  • variabel tumpukan
    Seumur hidup = ditentukan oleh Anda (baru untuk menghapus)
    visibilitas = ditentukan oleh Anda (apa pun penunjuk Anda)

  • variabel tumpukan
    visibilitas = dari iklan untuk keluar dari ruang lingkup
    Seumur hidup = dari iklan sampai ruang lingkup dinyatakan


(1) lebih tepatnya: dari inisialisasi ke de-inisialisasi unit kompilasi (mis., File C / C ++). Urutan inisialisasi unit kompilasi tidak ditentukan oleh standar.

(2) Hati-hati: jika Anda membuat variabel statis di header, setiap blok kompilasi mendapatkan salinannya sendiri.

30
06 янв. jawab diberikan peterchen 06 Jan 2009-01-06 01:46 '09 pada 1:46 2009-01-06 01:46

Saya yakin salah satu pedant akan segera menemukan jawaban terbaik, tetapi perbedaan utamanya adalah kecepatan dan ukuran.

Tumpukan

Jauh lebih cepat untuk dialokasikan. Ini dilakukan pada O (1), karena menonjol saat mengatur bingkai tumpukan, sehingga hampir gratis. Kerugiannya adalah jika Anda kehabisan ruang tumpukan, Anda mendapatkan tu>

tumpukan

Jauh lebih lambat untuk mengalokasikan. Tetapi Anda memiliki GB untuk bermain dengannya dan menunjuk ke sana.

Pengumpul sampah

Pengumpul sampah adalah kode yang berjalan di latar belakang dan membebaskan memori. Ketika Anda mengalokasikan memori untuk tumpukan, sangat mudah untuk melupakan untuk membebaskannya, yang disebut kebocoran memori. Seiring waktu, memori yang dikonsumsi oleh aplikasi Anda tumbuh dan tumbuh sampai berfungsi. Memiliki pemulung secara berkala membebaskan memori yang tidak lagi Anda perlukan, ini membantu menghi>

5
03 янв. Balas diberikan oleh Chris Smith pada 03 Jan 2009-01-03 09:06 '09 pada jam 9:06 2009-01-03 09:06

Apa masalah statika dan stack?

Masalah dengan distribusi "statis" adalah bahwa distribusi dilakukan pada waktu kompilasi: Anda tidak dapat menggunakannya untuk mendistribusikan sejumlah variabel data, jumlah yang tidak diketahui hingga waktu pelaksanaan.

Masalah dengan penumpukan adalah bahwa distribusi dihancurkan segera setelah subrutin yang melakukan alokasi dikembalikan.

Bisakah saya menulis seluruh aplikasi tanpa mengalokasikan variabel di heap?

Mungkin, tetapi bukan aplikasi non-sepele, normal, besar (tetapi yang disebut "built-in" program dapat ditulis tanpa tumpukan, menggunakan subset C ++).

Apa yang dilakukan oleh seorang pemulung?

Ini melacak data Anda ("tandai dan telusuri") untuk menentukan kapan aplikasi Anda tidak lagi merujuk padanya. Ini nyaman untuk aplikasi, karena aplikasi tidak perlu merilis data ... tetapi pengumpul sampah bisa mahal.

Pengumpul sampah bukanlah fitur pemrograman C ++ biasa.

Apa yang Anda bisa memanipulasi memori sendiri agar tidak menggunakan pengumpul sampah ini?

Periksa mekanisme C ++ untuk pelepasan memori deterministik:

  • 'statis': tidak pernah dirilis
  • 'stack': segera setelah variabel "melampaui"
  • 'heap': ketika pointer dihapus (dihapus secara eksplisit oleh aplikasi atau dihapus secara implisit dalam beberapa atau subrutin lain)
3
03 янв. ChrisW menjawab 03 Jan 2009-01-03 09:12 '09 jam 9:12 2009-01-03 09:12

Apa yang harus dilakukan jika program Anda tidak tahu sebelumnya berapa banyak memori yang dialokasikan (oleh karena itu, Anda tidak dapat menggunakan variabel tumpukan). Misalnya, daftar yang ditautkan, daftar dapat bertambah tanpa mengetahui sebelumnya ukurannya. Oleh karena itu, tumpukan pilihan masuk akal untuk daftar tertaut ketika Anda tidak tahu berapa banyak elemen yang akan dimasukkan ke dalamnya.

1
03 янв. jawabannya diberikan kal 03 januari. 2009-01-03 09:36 '09 pada 09:36 2009-01-03 09:36

Alokasi memori pada tumpukan (variabel fungsional, variabel lokal) dapat bermasalah jika tumpukan Anda terlalu dalam dan Anda meluap memori yang tersedia untuk mengalokasikan tumpukan. Heap ini ditujukan untuk objek yang perlu diakses dari banyak utas atau sepanjang siklus hidup program. Anda dapat menulis seluruh program tanpa menggunakan tumpukan.

Anda dapat dengan mudah menyaring memori tanpa pengumpul sampah, tetapi Anda juga dapat menentukan kapan objek dan memori dibebaskan. Saya mengalami masalah dengan Java ketika meluncurkan GC, dan saya memiliki proses waktu-nyata, karena GC adalah utas eksklusif (tidak ada lagi yang bisa dimulai). Karena itu, jika kinerja sangat penting, dan Anda dapat menjamin bahwa tidak ada objek yang bocor, menggunakan GC tidak terlalu berguna. Jika tidak, itu hanya membuat Anda membenci kehidupan ketika aplikasi Anda menghabiskan memori, dan Anda perlu melacak sumber kebocoran.

1
03 янв. Balas diberikan oleh Rob Elsner pada 03 Jan 2009-01-03 09:09 '09 pada 9:09 2009-01-03 09:09

Keuntungan HA dalam beberapa situasi adalah iritasi pada orang lain; Ketergantungan pada GC mendorong untuk tidak terlalu memikirkannya. Secara teori, ia menunggu sampai periode "idle" atau sampai benar-benar diperlukan, ketika mencuri bandwidth dan menyebabkan keterlambatan dalam respons dalam aplikasi Anda.

Tetapi Anda tidak perlu "tidak memikirkannya." Seperti semua hal lain dalam aplikasi multithreaded, ketika Anda bisa menyerah, Anda bisa menyerah. Jadi, misalnya, di .Net Anda dapat meminta GC; dengan melakukan ini, alih-alih peluncuran GC yang lebih jarang dan lebih lama, Anda dapat memiliki peluncuran GC yang lebih pendek dan singkat dan mendistribusikan penundaan yang terkait dengan data layanan ini.

Tapi ini mengalahkan daya tarik utama dari GC, yang tampaknya “tidak direkomendasikan untuk berpikir banyak tentang hal itu karena otomatis.”

Jika Anda memprogram untuk pertama kalinya sebelum GC menjadi umum dan merasa nyaman dengan malloc / free dan new / delete, maka mungkin Anda merasa bahwa GC agak menjengkelkan dan / atau mencurigakan (bagaimana mungkin ketidakpercayaan "optimasi" memiliki riwayat kotak-kotak.) Banyak aplikasi memungkinkan penundaan acak. Tetapi untuk aplikasi yang tidak, di mana latensi acak kurang dapat diterima, reaksi umum adalah untuk menghindari lingkungan GC dan bergerak ke arah kode yang tidak dikelola murni (atau dilarang, seni lama sekarat, bahasa assembly.)