Penggunaan stack dan heap yang tepat di C ++?

122

Saya telah memprogram untuk sementara waktu tetapi sebagian besar sudah Java dan C #. Saya tidak pernah benar-benar harus mengelola ingatan saya sendiri. Saya baru-baru ini mulai pemrograman dalam C ++ dan saya agak bingung kapan saya harus menyimpan sesuatu di stack dan kapan harus menyimpannya di heap.

Pemahaman saya adalah bahwa variabel yang sangat sering diakses harus disimpan di tumpukan dan objek, variabel yang jarang digunakan, dan struktur data yang besar semuanya harus disimpan di heap. Apakah ini benar atau saya salah?

Alexander
sumber

Jawaban:

242

Tidak, perbedaan antara tumpukan dan heap bukanlah performa. Umurnya: variabel lokal apa pun di dalam fungsi (apa pun yang tidak Anda malloc () atau baru) tinggal di tumpukan. Ini hilang ketika Anda kembali dari fungsi. Jika Anda ingin sesuatu berdurasi lebih lama dari fungsi yang mendeklarasikannya, Anda harus mengalokasikannya di heap.

class Thingy;

Thingy* foo( ) 
{
  int a; // this int lives on the stack
  Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
  Thingy *pointerToB = &B; // this points to an address on the stack
  Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
                                     // pointerToC contains its address.

  // this is safe: C lives on the heap and outlives foo().
  // Whoever you pass this to must remember to delete it!
  return pointerToC;

  // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
  // whoever uses this returned pointer will probably cause a crash!
  return pointerToB;
}

Untuk pemahaman yang lebih jelas tentang apa itu tumpukan, lihat dari ujung yang lain - daripada mencoba memahami apa yang dilakukan tumpukan dalam hal bahasa tingkat tinggi, cari "tumpukan panggilan" dan "konvensi pemanggilan" dan lihat apa mesin benar-benar melakukannya saat Anda memanggil suatu fungsi. Memori komputer hanyalah serangkaian alamat; "heap" dan "stack" adalah penemuan kompilator.

Crashworks
sumber
7
Akan lebih aman untuk menambahkan bahwa informasi dengan ukuran yang bervariasi biasanya berada di heap. Satu-satunya pengecualian yang saya ketahui adalah VLA di C99 (yang memiliki dukungan terbatas) dan fungsi alloca () yang sering disalahpahami bahkan oleh programmer C.
Dan Olson
10
Penjelasan yang baik, meskipun dalam skenario multithread dengan alokasi dan / atau deallocations yang sering, heap adalah titik perdebatan, sehingga memengaruhi performa. Namun, Cakupan hampir selalu menjadi faktor penentu.
peterchen
18
Tentu, dan new / malloc () itu sendiri merupakan operasi yang lambat, dan stack lebih cenderung berada di dcache daripada baris heap arbitrary. Ini adalah pertimbangan yang nyata, tetapi biasanya sekunder karena pertanyaan tentang umur.
Crashworks
1
Benarkah "Memori komputer hanyalah rangkaian alamat;" heap "dan" stack "adalah penemuan dari kompilasi" ?? Saya telah membaca di banyak tempat bahwa tumpukan adalah wilayah khusus dari memori komputer kita.
Vineeth Chitteti
2
@kai Itulah cara untuk memvisualisasikannya, tetapi belum tentu secara fisik benar. OS bertanggung jawab untuk mengalokasikan stack dan heap aplikasi. Kompilator juga bertanggung jawab, tetapi pada dasarnya ia bergantung pada OS untuk melakukannya. Stack terbatas, dan heap tidak. Ini karena cara OS menangani pengurutan alamat memori ini menjadi sesuatu yang lebih terstruktur sehingga beberapa aplikasi dapat berjalan di sistem yang sama. Heap dan stack bukan satu-satunya, tetapi biasanya hanya dua hal itu yang menjadi perhatian sebagian besar developer.
tsturzl
42

Saya akan mengatakan:

Simpan di tumpukan, jika Anda BISA.

Simpan di heap, jika PERLU.

Oleh karena itu, lebih suka tumpukan daripada heap. Beberapa kemungkinan alasan Anda tidak dapat menyimpan sesuatu di tumpukan adalah:

  • Ini terlalu besar - pada program multithread pada OS 32-bit, tumpukan memiliki ukuran kecil dan tetap (setidaknya pada saat pembuatan thread) (biasanya hanya beberapa MB. Ini agar Anda dapat membuat banyak thread tanpa menghabiskan alamat space Untuk program 64-bit, atau program single threaded (Linux), ini bukan masalah besar. Di Linux 32-bit, program single threaded biasanya menggunakan stack dinamis yang dapat terus berkembang hingga mencapai puncak heap.
  • Anda perlu mengaksesnya di luar cakupan bingkai tumpukan asli - ini benar-benar alasan utama.

Hal ini dimungkinkan, dengan kompiler yang masuk akal, untuk mengalokasikan objek berukuran tidak tetap pada heap (biasanya array yang ukurannya tidak diketahui pada waktu kompilasi).

MarkR
sumber
1
Apa pun yang lebih dari beberapa KB biasanya paling baik diletakkan di heap. Saya tidak tahu secara spesifik tapi saya tidak ingat pernah bekerja dengan tumpukan yang "beberapa MB".
Dan Olson
2
Itu adalah sesuatu yang tidak akan menjadi perhatian pengguna di awal. Untuk pengguna, vektor dan daftar tampaknya dialokasikan di stack meskipun STL menyimpan konten di heap. Pertanyaan sepertinya lebih pada memutuskan kapan harus secara eksplisit memanggil baru / hapus.
David Rodríguez - dribeas
1
Dan: Saya telah menempatkan 2 pertunjukan (Ya, G seperti di GIGS) ke tumpukan di bawah linux 32bit. Batas tumpukan bergantung pada OS.
Tuan Ree
6
Tuan: Tumpukan Nintendo DS adalah 16 kilobyte. Beberapa batas tumpukan bergantung pada perangkat keras.
Semut
Ant: Semua tumpukan bergantung pada perangkat keras, bergantung pada OS, dan juga bergantung pada kompiler.
Viliami
24

Ini lebih halus daripada yang disarankan oleh jawaban lain. Tidak ada pemisahan mutlak antara data di tumpukan dan data di heap berdasarkan cara Anda mendeklarasikannya. Sebagai contoh:

std::vector<int> v(10);

Dalam tubuh suatu fungsi, yang menyatakan a vector (larik dinamis) dari sepuluh bilangan bulat di tumpukan. Namun penyimpanannya dikelola olehvector tidak ada di tumpukan.

Ah, tapi (jawaban lain menyarankan) masa penyimpanan itu dibatasi oleh masa pakai vectoritu sendiri, yang di sini berbasis tumpukan, jadi tidak ada bedanya bagaimana penerapannya - kita hanya bisa memperlakukannya sebagai objek berbasis tumpukan dengan nilai semantik.

Tidak begitu. Misalkan fungsinya adalah:

void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

Jadi, apa pun yang memiliki swapfungsi (dan jenis nilai kompleks apa pun harus memilikinya) dapat berfungsi sebagai referensi rebindable ke beberapa data heap, di bawah sistem yang menjamin satu pemilik data tersebut.

Oleh karena itu, pendekatan C ++ modern adalah tidak pernah menyimpan alamat data heap dalam variabel pointer lokal polos. Semua alokasi heap harus disembunyikan di dalam kelas.

Jika Anda melakukannya, Anda dapat menganggap semua variabel dalam program Anda seolah-olah mereka adalah tipe nilai sederhana, dan melupakan heap sama sekali (kecuali saat menulis kelas pembungkus seperti nilai baru untuk beberapa data heap, yang seharusnya tidak biasa) .

Anda hanya perlu menyimpan satu pengetahuan khusus untuk membantu Anda mengoptimalkan: jika memungkinkan, alih-alih menugaskan satu variabel ke variabel lain seperti ini:

a = b;

tukar mereka seperti ini:

a.swap(b);

karena jauh lebih cepat dan tidak ada pengecualian. Satu-satunya persyaratan adalah Anda tidak perlu bterus memegang nilai yang sama (ini akan mendapatkan anilai, yang akan dibuanga = b ).

Sisi negatifnya adalah pendekatan ini memaksa Anda untuk mengembalikan nilai dari fungsi melalui parameter keluaran, bukan nilai pengembalian yang sebenarnya. Tapi mereka memperbaikinya di C ++ 0x dengan referensi rvalue .

Dalam situasi yang paling rumit dari semuanya, Anda akan membawa ide ini ke ekstrem umum dan menggunakan kelas penunjuk cerdas seperti shared_ptryang sudah ada di tr1. (Meskipun saya berpendapat bahwa jika Anda tampaknya membutuhkannya, Anda mungkin telah pindah ke luar sweet spot penerapan Standar C ++.)

Daniel Earwicker
sumber
6

Anda juga akan menyimpan item di heap jika perlu digunakan di luar lingkup fungsi yang membuatnya. Satu idiom yang digunakan dengan objek tumpukan disebut RAII - ini melibatkan penggunaan objek berbasis tumpukan sebagai pembungkus untuk sumber daya, ketika objek dimusnahkan, sumber daya akan dibersihkan. Objek berbasis tumpukan lebih mudah untuk dilacak saat Anda mungkin melempar pengecualian - Anda tidak perlu mengkhawatirkan diri Anda sendiri dengan menghapus objek berbasis tumpukan di penangan pengecualian. Inilah sebabnya mengapa pointer mentah biasanya tidak digunakan dalam C ++ modern, Anda akan menggunakan pointer pintar yang bisa menjadi pembungkus berbasis tumpukan untuk pointer mentah ke objek berbasis heap.

1800 INFORMASI
sumber
5

Untuk menambah jawaban lain, bisa juga tentang kinerja, setidaknya sedikit. Bukan berarti Anda harus mengkhawatirkan hal ini kecuali relevan untuk Anda, tetapi:

Mengalokasikan di heap membutuhkan pelacakan blok memori, yang bukan operasi waktu-konstan (dan memerlukan beberapa siklus dan overhead). Ini bisa menjadi lebih lambat karena memori menjadi terfragmentasi, dan / atau Anda hampir menggunakan 100% ruang alamat Anda. Di sisi lain, alokasi tumpukan adalah operasi waktu-konstan, pada dasarnya "bebas".

Hal lain yang perlu dipertimbangkan (sekali lagi, sangat penting hanya jika itu menjadi masalah) adalah biasanya ukuran tumpukan diperbaiki, dan bisa jauh lebih rendah daripada ukuran tumpukan. Jadi jika Anda mengalokasikan objek besar atau banyak objek kecil, Anda mungkin ingin menggunakan heap; jika Anda kehabisan ruang tumpukan, runtime akan menampilkan pengecualian tituler situs. Biasanya bukan masalah besar, tetapi hal lain yang perlu dipertimbangkan.

Nick
sumber
Baik heap & stack adalah memori virtual paged. Waktu pencarian heap sangat cepat dibandingkan dengan yang diperlukan untuk memetakan di memori baru. Di bawah Linux 32bit, saya dapat meletakkan> 2gig ke tumpukan saya. Di bawah Mac, saya pikir tumpukannya dibatasi hingga 65Meg.
Tn. Ree
3

Stack lebih efisien, dan data cakupan yang lebih mudah dikelola.

Tetapi heap harus digunakan untuk apa pun yang lebih besar dari beberapa KB (mudah di C ++, cukup buat boost::scoped_ptrdi tumpukan untuk menahan penunjuk ke memori yang dialokasikan).

Pertimbangkan algoritma rekursif yang terus memanggil dirinya sendiri. Sangat sulit untuk membatasi dan atau menebak total penggunaan tumpukan! Sedangkan di heap, pengalokasi ( malloc()atau new) dapat menunjukkan kehabisan memori dengan mengembalikan NULLatau throwing.

Sumber : Kernel Linux yang tumpukannya tidak lebih dari 8KB!

unixman83
sumber
Untuk referensi bagi pembaca lain: (A) Yang "harus" di sini adalah murni pendapat pribadi pengguna, diambil dari paling banyak 1 kutipan dan 1 skenario yang tidak mungkin ditemui oleh banyak pengguna (rekursi). Juga, (B) yang disediakan oleh pustaka standar std::unique_ptr, yang seharusnya lebih disukai daripada pustaka eksternal seperti Boost (meskipun hal itu memenuhi standar dari waktu ke waktu).
underscore_d
2

Untuk kelengkapannya, Anda dapat membaca artikel Miro Samek tentang masalah penggunaan heap dalam konteks perangkat lunak yang disematkan .

Tumpukan Masalah

Daniel Daranas
sumber
1

Pilihan apakah akan mengalokasikan di heap atau di stack adalah salah satu pilihan yang dibuat untuk Anda, bergantung pada bagaimana variabel Anda dialokasikan. Jika Anda mengalokasikan sesuatu secara dinamis, menggunakan panggilan "baru", Anda mengalokasikan dari heap. Jika Anda mengalokasikan sesuatu sebagai variabel global, atau sebagai parameter dalam fungsi, itu dialokasikan di stack.

Rob Lachlan
sumber
4
Saya curiga dia bertanya kapan harus meletakkan sesuatu di heap, bukan bagaimana.
Steve Rowe
0

Menurut saya ada dua faktor penentu

1) Scope of variable
2) Performance.

Saya lebih suka menggunakan stack dalam banyak kasus, tetapi jika Anda memerlukan akses ke variabel di luar cakupan, Anda dapat menggunakan heap.

Untuk meningkatkan kinerja saat menggunakan heap, Anda juga dapat menggunakan fungsionalitas tersebut untuk membuat blok heap dan yang dapat membantu dalam mendapatkan kinerja daripada mengalokasikan setiap variabel di lokasi memori yang berbeda.

anand
sumber
0

mungkin ini telah dijawab dengan cukup baik. Saya ingin mengarahkan Anda ke rangkaian artikel di bawah ini untuk memiliki pemahaman yang lebih dalam tentang detail tingkat rendah. Alex Darby memiliki serangkaian artikel, di mana dia memandu Anda dengan debugger. Ini Bagian 3 tentang Stack. http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/

hAcKnRoCk
sumber
Tautan tampaknya mati, tetapi memeriksa Mesin Wayback Arsip Internet menunjukkan bahwa itu hanya berbicara tentang tumpukan dan oleh karena itu tidak melakukan apa pun untuk menjawab pertanyaan spesifik di sini tentang tumpukan versus tumpukan . -1
underscore_d