Pertanyaan Apa yang terjadi jika 'melempar' gagal mengalokasikan memori untuk objek pengecualian?


Dari standar C ++ 11 (15.1.p4):

Memori untuk objek pengecualian dialokasikan dalam yang tidak ditentukan   cara, kecuali seperti dicatat dalam 3.7.4.1

Bagaimana jika alokasi gagal - apakah itu akan dibuang std::bad_alloc sebagai gantinya? Panggilan std::terminate? Tidak ditentukan?


32
2017-08-04 03:09


asal


Jawaban:


(memberikan jawaban saya sendiri ... Saya akan menunggu beberapa hari dan jika tidak ada masalah dengan itu - saya akan menandainya sebagai diterima)

Saya menghabiskan beberapa waktu menyelidiki ini dan di sini adalah apa yang saya gali:

  • Standar C ++ tidak menentukan apa yang akan terjadi dalam kasus ini
  • Clang dan GCC sepertinya digunakan C ++ Itanium ABI

Itanimum ABI menyarankan untuk menggunakan heap untuk pengecualian:

Penyimpanan diperlukan untuk pengecualian yang dibuang. Penyimpanan ini harus   tetap ada saat tumpukan sedang dibatalkan, karena akan digunakan oleh   handler, dan harus aman dari benang. Penyimpanan objek pengecualian akan   oleh karena itu biasanya dialokasikan di heap

...

Memori akan dialokasikan oleh __cxa_allocate_exception rutinitas perpustakaan runtime.

Jadi, ya ... melempar pengecualian kemungkinan akan melibatkan penguncian muteks dan mencari blok memori gratis. :-(

Ini juga menyebutkan ini:

Jika __cxa_allocate_exception tidak dapat mengalokasikan objek pengecualian di bawah batasan ini, ia memanggil mengakhiri ()

Ya ... di GCC dan Clang "buang myX ();" dapat membunuh aplikasi Anda dan Anda tidak bisa berbuat apa-apa (mungkin menulis sendiri __cxa_allocate_exception dapat membantu - tetapi tentu saja tidak akan portabel)

Itu menjadi lebih baik:

3.4.1 Mengalokasikan Objek Pengecualian

Memori untuk objek pengecualian akan dialokasikan oleh   __cxa_allocate_exception perpustakaan runtime rutin, dengan persyaratan umum seperti yang dijelaskan dalam Bagian 2.4.2. Jika alokasi normal   gagal, maka itu akan mencoba untuk mengalokasikan salah satu buffer darurat,   dijelaskan dalam Bagian 3.3.1, di bawah batasan berikut:

  • Ukuran objek pengecualian, termasuk header, di bawah 1KB.
  • Untaian saat ini belum memiliki empat buffer.
  • Ada kurang dari 16 utas lainnya yang menahan buffer, atau utas ini   akan menunggu sampai salah satu dari yang lain melepaskan buffernya sebelum memperolehnya.

Ya, program Anda hanya bisa bertahan! Kesempatan yang diberikan ini kecil - Anda perlu membuang memori dan benang Anda harus menggunakan semua 16 buffer darurat dan masukkan menunggu utas lain yang seharusnya menghasilkan pengecualian. Tetapi jika Anda melakukan banyak hal std::current_exception (seperti pengecualian rantai dan melewatkannya di antara untaian) - itu tidak terlalu mustahil.

Kesimpulan:

Ini adalah kekurangan dalam standar C ++ - Anda tidak dapat menulis program yang 100% andal (yang menggunakan pengecualian). Contoh buku teks adalah server yang menerima koneksi dari klien dan menjalankan tugas yang dikirim. Pendekatan yang jelas untuk menangani masalah adalah membuang pengecualian, yang akan melonggarkan segalanya dan menutup koneksi - semua klien lain tidak akan terpengaruh dan server akan terus beroperasi (bahkan dalam kondisi memori rendah). Sayangnya, server semacam itu tidak mungkin ditulis dalam C ++.

Anda dapat mengklaim bahwa sistem modern (yaitu Linux) akan membunuh server seperti itu sebelum kita mencapai situasi ini. Tetapi (1) itu bukan argumen; (2) manajer memori dapat diatur untuk terlalu banyak; (3) Pembunuh OOM tidak akan dipicu untuk aplikasi 32-bit yang berjalan pada perangkat keras 64bit dengan memori yang cukup (atau jika aplikasi membatasi memori secara artifisial).

Pada catatan pribadi saya sangat marah tentang penemuan ini - selama bertahun-tahun saya mengklaim bahwa kode saya menangani out-of-memory dengan anggun. Ternyata saya berbohong kepada klien saya. :-( Mungkin juga mulai memotong alokasi memori, panggil std::terminate dan memperlakukan semua fungsi terkait sebagai noexcept - ini tentu akan membuat hidup saya lebih mudah (coding-wise). Tidak heran mereka masih menggunakan Ada untuk memprogram roket.


8
2017-08-07 17:46



[intro.compliance] / 2 Meskipun Standar Internasional ini hanya menyatakan persyaratan pada implementasi C ++, persyaratan tersebut seringkali lebih mudah dipahami jika mereka diutarakan sebagai persyaratan pada program, bagian dari program, atau pelaksanaan program. Persyaratan seperti itu memiliki arti berikut:

(2.1) - Jika suatu program tidak mengandung pelanggaran aturan dalam Standar Internasional ini, implementasi yang sesuai harus, dalam batas sumber dayanya, menerima dan menjalankan program itu dengan benar.

Penekanan tambang. Pada dasarnya, standar membayangkan kegagalan untuk mengalokasikan memori dinamis (dan mengatur perilaku dalam kasus ini), tetapi tidak ada jenis memori lainnya; dan tidak meresepkan cara apa pun yang harus dilakukan oleh implementasi ketika batas sumber dayanya tercapai.

Contoh lain adalah kehabisan tumpukan karena rekursi yang terlalu dalam. Tidak ada standar yang mengatakan seberapa dalam rekursi diperbolehkan. Luapan tumpukan yang dihasilkan adalah implementasi yang menjalankan "dalam batas sumber daya" kanan-ke-gagal.


2
2017-08-05 14:25



Jawaban saat ini sudah menjelaskan apa yang dilakukan oleh GCC. Saya telah memeriksa perilaku MSVC - itu mengalokasikan pengecualian pada stack, sehingga alokasi tidak bergantung pada heap. Hal ini membuat stack overflow dimungkinkan (objek pengecualian dapat menjadi besar), tetapi penanganan overflow stack tidak tercakup oleh standar C ++.

Saya menggunakan program singkat ini untuk memeriksa apa yang terjadi selama lemparan pengecualian:

#include <iostream>

class A {
public:
    A() { std::cout << "A::A() at " << static_cast<void *>(this) << std::endl; }
    A(const A &) { std::cout << "A::A(const A &) at " << static_cast<void *>(this) << std::endl; }
    A(A &&) { std::cout << "A::A(A &&) at " << static_cast<void *>(this) << std::endl; }
    ~A() { std::cout << "A::~A() at " << static_cast<void *>(this) << std::endl; }
    A &operator=(const A &) = delete;
    A &operator=(A &&) = delete;
};

int main()
{
    try {
        try {
            try {
                A a;
                throw a;
            } catch (const A &ex) {
                throw;
            }
        } catch (const A &ex) {
            throw;
        }
    } catch (const A &ex) {
    }
}

Ketika membangun dengan output GCC jelas menunjukkan bahwa pengecualian dilemparkan sedang dialokasikan jauh dari tumpukan:

A::A() at 0x22cad7
A::A(A &&) at 0x600020510
A::~A() at 0x22cad7
A::~A() at 0x600020510

Ketika membangun dengan output MSVC menunjukkan bahwa pengecualian dialokasikan di dekatnya pada stack:

A::A() at 000000000018F4E4
A::A(A &&) at 000000000018F624
A::~A() at 000000000018F4E4
A::~A() at 000000000018F624

Pemeriksaan tambahan dengan debugger menunjukkan bahwa handler dan destruktor ditangkap dieksekusi di bagian atas tumpukan, sehingga konsumsi tumpukan meningkat dengan setiap blok tangkap dimulai dengan lemparan pertama dan sampai std::uncaught_exceptions() menjadi 0.

Perilaku seperti itu berarti bahwa penanganan kehabisan memori yang benar mengharuskan Anda untuk membuktikan bahwa ada cukup ruang untuk program untuk mengeksekusi penangan pengecualian dan semua perusak di jalan.

Untuk membuktikan hal yang sama dengan GCC, Anda harus membuktikan tidak ada lebih dari empat pengecualian tersarang dan pengecualian memiliki ukuran kurang dari 1KiB (ini termasuk header). Selain itu, jika beberapa utas memiliki lebih dari empat pengecualian tersarang, Anda juga perlu membuktikan tidak ada deadlock yang disebabkan oleh alokasi buffer darurat.


2
2017-10-16 06:21



Sebenarnya ditentukan bahwa jika alokasi untuk objek pengecualian gagal, bad_alloc harus dibuang dan implementasi juga bisa memanggil penangan baru.

Ini adalah apa yang sebenarnya ditentukan di bagian c ++ standar (§3.7.4.1) situs Anda [basic.stc.dynamic.allocation]:

Fungsi alokasi yang gagal mengalokasikan penyimpanan dapat menjalankan fungsi handler baru yang diinstal saat ini   (21.6.3.3), jika ada. [   catatan:   Fungsi alokasi yang disediakan program dapat memperoleh alamat saat ini   diinstal   new_handler   menggunakan   std :: get_new_handler   fungsi (21.6.3.4).   - catatan akhir   ] Jika suatu alokasi   fungsi yang memiliki spesifikasi pengecualian non-lempar (18.4) gagal mengalokasikan penyimpanan, itu akan mengembalikan null   penunjuk. Fungsi alokasi lainnya yang gagal mengalokasikan penyimpanan harus menunjukkan kegagalan hanya dengan melempar sebuah   pengecualian (18.1) dari tipe yang akan cocok dengan tipe handler (18.3)   std :: bad_alloc   (21.6.3.1).

Maka ini teringat masuk [except.terminate]

Dalam beberapa situasi penanganan pengecualian harus ditinggalkan untuk teknik penanganan kesalahan yang kurang halus. [   catatan:   Situasi ini adalah:   -   (1.1)   ketika mekanisme penanganan pengecualian, setelah menyelesaikan inisialisasi objek eksepsi tapi   sebelum aktivasi handler untuk pengecualian (18.1) *

Jadi itanium ABI tidak mengikuti spesifikasi standar c ++, karena dapat memblokir atau memanggil terminate jika program gagal mengalokasikan memori untuk objek pengecualian.


0
2017-10-15 10:38