Masalah Khusus untuk Bahasa C ++
Pertama-tama, tidak ada yang disebut alokasi "tumpukan" atau "tumpukan" yang diamanatkan oleh C ++ . Jika Anda berbicara tentang objek otomatis dalam lingkup blok, mereka bahkan tidak "dialokasikan". (BTW, durasi penyimpanan otomatis dalam C jelas TIDAK sama dengan "dialokasikan"; yang terakhir adalah "dinamis" dalam bahasa C ++.) Memori yang dialokasikan secara dinamis ada di toko bebas , tidak harus pada "tumpukan", meskipun yang terakhir sering kali merupakan implementasi (default) .
Meskipun sesuai aturan semantik mesin abstrak , objek otomatis masih menempati memori, implementasi C ++ yang sesuai diizinkan untuk mengabaikan fakta ini ketika dapat membuktikan bahwa ini tidak masalah (ketika itu tidak mengubah perilaku yang dapat diamati dari program). Izin ini diberikan oleh aturan seolah-olah dalam ISO C ++, yang juga merupakan klausa umum yang memungkinkan optimasi yang biasa (dan ada juga aturan yang hampir sama dalam ISO C). Selain aturan as-if, ISO C ++ juga memiliki aturan penyalinan salinan untuk memungkinkan penghilangan ciptaan objek tertentu. Karena itu panggilan konstruktor dan destruktor yang terlibat dihilangkan. Akibatnya, objek otomatis (jika ada) dalam konstruktor dan destruktor ini juga dihilangkan, dibandingkan dengan semantik abstrak naif yang tersirat oleh kode sumber.
Di sisi lain, alokasi toko gratis pasti "alokasi" oleh desain. Di bawah aturan ISO C ++, alokasi semacam itu dapat dicapai dengan panggilan fungsi alokasi . Namun, sejak ISO C ++ 14, ada aturan baru (non-as-if) untuk memungkinkan penggabungan fungsi alokasi global (yaitu ::operator new
) panggilan dalam kasus tertentu. Jadi bagian-bagian dari operasi alokasi dinamis juga dapat menjadi tidak-suka seperti halnya objek otomatis.
Fungsi alokasi mengalokasikan sumber daya memori. Objek dapat selanjutnya dialokasikan berdasarkan alokasi menggunakan pengalokasi. Untuk objek otomatis, mereka disajikan langsung - meskipun memori yang mendasarinya dapat diakses dan digunakan untuk memberikan memori ke objek lain (dengan penempatannew
), tetapi ini tidak masuk akal seperti toko bebas, karena tidak ada cara untuk memindahkan sumber daya di tempat lain.
Semua masalah lain berada di luar cakupan C ++. Meskipun demikian, mereka masih signifikan.
Tentang Implementasi C ++
C ++ tidak memaparkan catatan aktivasi yang direvisi atau semacam kelanjutan kelas satu (misalnya oleh yang terkenal call/cc
), tidak ada cara untuk secara langsung memanipulasi frame rekaman aktivasi - di mana implementasi perlu menempatkan objek otomatis. Setelah tidak ada interoperasi (non-portabel) dengan implementasi yang mendasari ("asli" kode non-portabel, seperti kode assembly inline), penghilangan alokasi yang mendasari frame bisa sangat sepele. Sebagai contoh, ketika fungsi yang dipanggil digarisbawahi, frame dapat secara efektif digabung menjadi yang lain, sehingga tidak ada cara untuk menunjukkan apa itu "alokasi".
Namun, begitu interops dihormati, segalanya menjadi kompleks. Implementasi khas C ++ akan mengekspos kemampuan interop pada ISA (arsitektur set-instruksi) dengan beberapa konvensi pemanggilan sebagai batas biner yang dibagikan dengan kode asli (mesin level ISA). Ini akan secara eksplisit mahal, terutama, ketika mempertahankan penunjuk tumpukan , yang sering dipegang langsung oleh register tingkat ISA (dengan instruksi mesin yang mungkin khusus untuk diakses). Penunjuk tumpukan menunjukkan batas bingkai atas panggilan fungsi (saat ini aktif). Ketika panggilan fungsi dimasukkan, bingkai baru diperlukan dan penunjuk tumpukan ditambahkan atau dikurangi (tergantung pada konvensi ISA) dengan nilai tidak kurang dari ukuran bingkai yang diperlukan. Bingkai ini kemudian dikatakan dialokasikanketika stack pointer setelah operasi. Parameter fungsi dapat diteruskan ke bingkai tumpukan juga, tergantung pada konvensi pemanggilan yang digunakan untuk panggilan. Bingkai dapat menampung memori objek otomatis (mungkin termasuk parameter) yang ditentukan oleh kode sumber C ++. Dalam arti implementasi seperti itu, objek-objek ini "dialokasikan". Ketika kontrol keluar dari panggilan fungsi, bingkai tidak lagi diperlukan, biasanya dilepaskan dengan mengembalikan penunjuk tumpukan kembali ke keadaan sebelum panggilan (disimpan sebelumnya sesuai dengan konvensi pemanggilan). Ini dapat dilihat sebagai "deallokasi". Operasi-operasi ini membuat catatan aktivasi secara efektif struktur data LIFO, sehingga sering disebut " tumpukan (panggilan) ".
Karena sebagian besar implementasi C ++ (terutama yang menargetkan kode asli tingkat ISA dan menggunakan bahasa rakitan sebagai output langsungnya) menggunakan strategi serupa seperti ini, skema "alokasi" yang membingungkan sangat populer. Alokasi seperti itu (juga deallocations) menghabiskan siklus alat berat, dan itu bisa mahal ketika panggilan (yang tidak dioptimalkan) sering terjadi, meskipun arsitektur mikro CPU modern dapat memiliki optimasi yang kompleks diimplementasikan oleh perangkat keras untuk pola kode umum (seperti menggunakan stack engine dalam mengimplementasikan PUSH
/ POP
instruksi).
Tapi bagaimanapun, secara umum, memang benar bahwa biaya alokasi bingkai tumpukan secara signifikan lebih kecil daripada panggilan ke fungsi alokasi yang mengoperasikan toko gratis (kecuali itu benar-benar dioptimalkan jauh) , yang itu sendiri dapat memiliki ratusan (jika tidak jutaan :-) operasi untuk mempertahankan stack pointer dan status lainnya. Fungsi alokasi biasanya didasarkan pada API yang disediakan oleh lingkungan yang dihosting (misalnya runtime yang disediakan oleh OS). Berbeda dengan tujuan memegang objek otomatis untuk panggilan fungsi, alokasi tersebut bertujuan umum, sehingga mereka tidak akan memiliki struktur bingkai seperti tumpukan. Secara tradisional, mereka mengalokasikan ruang dari penyimpanan kolam yang disebut heap (atau beberapa heaps). Berbeda dari "tumpukan", konsep "tumpukan" di sini tidak menunjukkan struktur data yang digunakan;ini berasal dari implementasi bahasa awal beberapa dekade yang lalu . (BTW, tumpukan panggilan biasanya dialokasikan dengan ukuran tetap atau yang ditentukan pengguna dari tumpukan oleh lingkungan dalam program atau utas startup.) Sifat kasus penggunaan membuat alokasi dan deallokasi dari tumpukan jauh lebih rumit (daripada push atau pop of tumpukan frame), dan hampir tidak mungkin dioptimalkan secara langsung oleh perangkat keras.
Efek pada Akses Memori
Alokasi tumpukan yang biasa selalu menempatkan frame baru di atas, sehingga memiliki lokalitas yang cukup baik. Ini cocok untuk cache. OTOH, memori yang dialokasikan secara acak di toko gratis tidak memiliki properti seperti itu. Sejak ISO C ++ 17, ada templat sumber daya kumpulan yang disediakan oleh <memory>
. Tujuan langsung dari antarmuka tersebut adalah untuk memungkinkan hasil alokasi berturut-turut berdekatan dalam memori. Ini mengakui fakta bahwa strategi ini umumnya baik untuk kinerja dengan implementasi kontemporer, misalnya ramah terhadap cache dalam arsitektur modern. Ini tentang kinerja akses daripada alokasi .
Konkurensi
Harapan akses bersamaan ke memori dapat memiliki efek yang berbeda antara tumpukan dan tumpukan. Tumpukan panggilan biasanya secara eksklusif dimiliki oleh satu utas eksekusi dalam implementasi C ++. OTOH, tumpukan sering dibagi di antara utas dalam suatu proses. Untuk tumpukan seperti itu, fungsi alokasi dan deallokasi harus melindungi struktur data administrasi internal bersama dari ras data. Akibatnya, alokasi tumpukan dan deokasiasi mungkin memiliki overhead tambahan karena operasi sinkronisasi internal.
Efisiensi Ruang
Karena sifat kasus penggunaan dan struktur data internal, tumpukan mungkin menderita fragmentasi memori internal , sedangkan tumpukan tidak. Ini tidak memiliki dampak langsung pada kinerja alokasi memori, tetapi dalam sistem dengan memori virtual , efisiensi ruang yang rendah dapat menurunkan kinerja keseluruhan dari akses memori. Ini sangat mengerikan ketika HDD digunakan sebagai pertukaran memori fisik. Ini dapat menyebabkan latensi yang cukup lama - terkadang milyaran siklus.
Keterbatasan Alokasi Tumpukan
Meskipun alokasi tumpukan sering unggul dalam kinerja daripada alokasi tumpukan pada kenyataannya, itu tentu saja tidak berarti alokasi tumpukan selalu dapat menggantikan alokasi tumpukan.
Pertama, tidak ada cara untuk mengalokasikan ruang pada stack dengan ukuran yang ditentukan saat runtime dengan cara portabel dengan ISO C ++. Ada ekstensi yang disediakan oleh implementasi seperti alloca
dan VLA G ++ (array variabel-panjang), tetapi ada alasan untuk menghindarinya. (IIRC, sumber Linux menghapus penggunaan VLA baru-baru ini.) (Juga perhatikan ISO C99 memang telah mengamanatkan VLA, tetapi ISO C11 mengubah dukungan opsional.)
Kedua, tidak ada cara yang andal dan portabel untuk mendeteksi kelelahan ruang stack. Ini sering disebut stack overflow (hmm, etimologi situs ini) , tetapi mungkin lebih akurat, stack overrun . Pada kenyataannya, ini sering menyebabkan akses memori tidak valid, dan keadaan program kemudian rusak (... atau mungkin lebih buruk, lubang keamanan). Faktanya, ISO C ++ tidak memiliki konsep "stack" dan membuatnya tidak terdefinisi ketika sumber dayanya habis . Berhati-hatilah dengan berapa banyak ruang yang tersisa untuk objek otomatis.
Jika ruang stack habis, ada terlalu banyak objek yang dialokasikan dalam stack, yang dapat disebabkan oleh terlalu banyak panggilan fungsi atau penggunaan objek otomatis yang tidak tepat. Kasus-kasus seperti itu mungkin menunjukkan adanya bug, misalnya panggilan fungsi rekursif tanpa kondisi keluar yang benar.
Namun demikian, panggilan rekursif yang mendalam kadang-kadang diinginkan. Dalam implementasi bahasa yang membutuhkan dukungan panggilan aktif tidak terikat (di mana kedalaman panggilan hanya dibatasi oleh total memori), tidak mungkin untuk menggunakan tumpukan panggilan asli (kontemporer) secara langsung sebagai catatan aktivasi bahasa target seperti implementasi C ++ yang khas. Untuk mengatasi masalah tersebut, diperlukan cara alternatif untuk membangun catatan aktivasi. Sebagai contoh, SML / NJ secara eksplisit mengalokasikan frame pada heap dan menggunakan tumpukan kaktus . Alokasi rumit dari bingkai catatan aktivasi semacam itu biasanya tidak secepat bingkai tumpukan panggilan. Namun, jika bahasa tersebut diimplementasikan lebih lanjut dengan jaminan rekursi ekor yang tepat, alokasi tumpukan langsung dalam bahasa objek (yaitu, "objek" dalam bahasa tidak disimpan sebagai referensi, tetapi nilai primitif asli yang dapat dipetakan satu-ke-satu ke objek C ++ yang dibagikan) bahkan lebih rumit dengan lebih banyak penalti kinerja secara umum. Saat menggunakan C ++ untuk mengimplementasikan bahasa seperti itu, sulit untuk memperkirakan dampak kinerja.