Seberapa penting Anda menemukan keamanan pengecualian dalam kode C ++ Anda?


19

Setiap kali saya menganggap membuat kode saya sangat aman, saya membenarkan tidak melakukannya karena itu akan sangat memakan waktu. Pertimbangkan cuplikan yang relatif sederhana ini:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Untuk menerapkan jaminan pengecualian dasar, saya bisa menggunakan pointer lingkup pada new panggilan. Ini akan mencegah kebocoran memori jika ada panggilan yang melempar pengecualian.

Namun, katakanlah saya ingin menerapkan jaminan pengecualian yang kuat. Setidaknya, saya perlu menerapkan pointer bersama untuk kontainer saya (saya tidak menggunakan Boost), sebuah nothrow Entity::Swapuntuk menambahkan komponen secara atom, dan semacam idiom untuk menambahkan secara atom ke keduaVector dan Map. Tidak hanya akan memakan waktu untuk mengimplementasikan, tetapi mereka akan mahal karena melibatkan lebih banyak menyalin daripada pengecualian solusi yang tidak aman.

Pada akhirnya, saya merasa seperti waktu yang dihabiskan untuk melakukan semua itu tidak akan dibenarkan hanya karena CreateEntityfungsi sederhana sangat pengecualian aman. Saya mungkin hanya ingin game menampilkan kesalahan dan menutup pada titik itu pula.

Seberapa jauh Anda mengambil ini dalam proyek game Anda sendiri? Apakah secara umum dapat diterima untuk menulis pengecualian kode tidak aman untuk suatu program yang bisa crash ketika ada pengecualian?


21
Ketika saya bekerja pada proyek-proyek C ++ di masa lalu, pada dasarnya kami berpura-pura pengecualian tidak ada.
Tetrad

2
Saya, secara pribadi, menyukai pengecualian dan sering benar-benar menikmati mencari tahu bagaimana membuat kode saya memberikan jaminan yang kuat. Jika Anda hanya akan crash ketika Anda mendapatkan pengecualian ... mungkin tidak repot-repot dengan itu.

7
Saya pribadi menggunakan pengecualian untuk mengelola .. hm .. 'pengecualian' bukan kesalahan. Seperti, jika saya tahu bahwa suatu fungsi mungkin macet, saya membiarkannya macet, tetapi jika saya tahu bahwa suatu fungsi mungkin tidak berhasil membaca arsip, tetapi ada cara lain, saya mengelilinginya dengan mencoba, menangkap. dan jika ini merupakan pengecualian "tidak dapat membaca", saya hanya mengaturnya dengan cara lain yang akan saya lakukan tanpa pembacaan arsip.
Gustavo Maciel

Apa yang dikatakan Gtoknu, Anda harus mengetahui pengecualian mana yang bisa dibuang oleh setiap pustakawan eksternal.
Peter Ølsted

Jawaban:


26

Jika Anda akan menggunakan pengecualian (yang bukan berarti Anda harus, ada pro dan kontra untuk pendekatan yang berada di luar lingkup spesifik dari pertanyaan ini), Anda harus menulis dengan benar pengecualian kode aman.

Kode yang tidak menggunakan pengecualian dan secara eksplisit tidak terkecuali aman lebih baik daripada kode yang menggunakannya, tetapi hanya setengah-menjamin keselamatan pengecualiannya. Jika Anda memiliki kasing yang terakhir, Anda memiliki seluruh kelas bug dan kegagalan yang dapat terjadi ketika pengecualian terjadi yang Anda mungkin tidak dapat pulih sama sekali, menjadikan salah satu manfaat pengecualian sepenuhnya batal dan tidak berlaku. Bahkan jika itu adalah kelas kegagalan yang relatif jarang, itu masih mungkin.

Ini bukan untuk mengatakan bahwa jika Anda menggunakan pengecualian, semua kode harus memberikan jaminan pengecualian yang kuat - hanya saja setiap bagian kode harus memberikan semacam jaminan (tidak ada, dasar, kuat) sehingga dalam konsumsi kode itu Anda tahu jaminan mana yang bisa diberikan konsumen. Umumnya semakin rendah level komponen yang dipermasalahkan, semakin kuat pula jaminannya.

Jika Anda tidak akan pernah memberikan jaminan yang kuat atau tidak akan pernah benar-benar merangkul paradigma penanganan perkecualian dan semua pekerjaan tambahan dan kerugian yang menyiratkan, Anda juga tidak akan benar-benar mendapatkan semua keuntungan yang disiratkan dan Anda mungkin memiliki yang lebih mudah waktu hanya meninggalkan pengecualian sama sekali.


12
Ini saja bernilai +1: "Kode yang tidak menggunakan pengecualian dan secara eksplisit tidak terkecuali aman lebih baik daripada kode yang menggunakannya tetapi setengah-setengah menganggap keamanan pengecualiannya."
Maximus Minimus

14

Aturan umum saya: Jika Anda perlu menggunakan pengecualian, gunakan pengecualian. Jika Anda tidak perlu menggunakan pengecualian, jangan gunakan pengecualian. Jika Anda tidak tahu apakah Anda perlu menggunakan pengecualian atau tidak, Anda tidak perlu menggunakan pengecualian.

Pengecualian ada untuk menjadi garis pertahanan terakhir mutlak Anda dalam aplikasi yang selalu kritis. Jika Anda menulis perangkat lunak kontrol lalu lintas udara yang benar-benar tidak pernah gagal, gunakan pengecualian. Jika Anda menulis kode kontrol untuk pembangkit listrik tenaga nuklir, gunakan pengecualian.

Sebaliknya, saat mengembangkan gim, Anda jauh lebih baik mengikuti mantra: Kecelakaan awal, tabrak keras. Jika ada masalah, Anda benar-benar ingin mengetahuinya sesegera mungkin; Anda tidak pernah ingin kesalahan tidak terlihat 'ditangani', karena ini akan sering menyembunyikan penyebab sebenarnya dari kelakuan yang keliru kemudian dan membuat proses debug jauh lebih sulit daripada yang seharusnya.


5

Masalah dengan pengecualian adalah Anda harus menghadapinya. Jika Anda mendapatkan pengecualian karena kehabisan memori maka kemungkinan Anda tidak akan dapat menangani kesalahan itu. Mungkin juga hanya membuang seluruh permainan dalam 1 handler pengecualian raksasa yang muncul kotak kesalahan.

Untuk pengembangan game, saya lebih suka mencoba dan menemukan cara untuk membuat kode saya dengan anggun melanjutkan kegagalan jika memungkinkan.

Misalnya saya akan memasukkan kode ERROR khusus ke dalam permainan saya. Ini adalah lingkaran merah yang berputar dengan X putih di atasnya dan perbatasan putih. Tergantung pada gim yang tidak terlihat mungkin lebih baik. Ini memiliki satu contoh tunggal diinisialisasi pada awal. Karena kode tersebut dimasukkan ke dalam kode daripada menggunakan file eksternal apa pun, tidak mungkin gagal.

Dalam hal panggilan loadMesh yang normal gagal alih-alih melemparkan kembali beberapa kesalahan dan menabrak desktop itu diam-diam akan mencatat kegagalan ke konsol / logfile dan mengembalikan mesh kesalahan hardcoded. Saya menemukan bahwa Skyrim sebenarnya melakukan hal yang sama ketika seorang mod mengacaukan semuanya. Ada kemungkinan bagus bahwa pemain bahkan tidak akan melihat kesalahan mesh (kecuali ada beberapa masalah besar dan banyak dari mereka menyukainya).

Ada shader fallback juga. Salah satu yang melakukan operasi matriks vertex dasar tetapi jika tidak ada matriks yang seragam karena alasan tertentu ia masih harus melakukan pandangan ortogonal. Ia tidak memiliki naungan / pencahayaan / bahan yang tepat (meskipun ia memiliki flag color_overide sehingga saya dapat menggunakannya dengan mesh kesalahan saya).

Satu-satunya masalah yang mungkin adalah jika versi galat kesalahan bisa secara permanen merusak permainan dalam beberapa cara. Sebagai contoh memastikan bahwa itu tidak berlaku fisika karena jika pemain memuat suatu area, maka mesh kesalahan tendangan di dalamnya mungkin jatuh ke lantai kemudian jika mereka memuat ulang permainan kali ini dengan kerja mesh yang benar, jika lebih besar itu mungkin tertanam di tanah. Itu seharusnya tidak terlalu sulit karena Anda mungkin akan menyimpan jerat fisika Anda dengan yang grafis Anda. Scripting juga bisa menjadi masalah. Dengan beberapa hal Anda hanya perlu mati keras. Tidak benar-benar yakin apa yang akan menggunakan 'level' mundur akan (walaupun Anda bisa membuat sebuah ruangan kecil dengan tanda mengatakan ada kesalahan, pastikan saja save dinonaktifkan karena Anda tidak ingin pemain terjebak di dalamnya).

Dalam kasus 'baru', untuk pengembangan game mungkin lebih baik untuk melihat menggunakan semacam pabrik. Ini dapat memberi Anda berbagai keuntungan lainnya seperti objek preallocating sebelum Anda benar-benar membutuhkannya dan / atau membuatnya secara massal. Ini juga membuatnya lebih mudah untuk membuat hal-hal seperti indeks global objek yang dapat Anda gunakan untuk manajemen memori, menemukan hal-hal dengan id, (walaupun Anda juga bisa melakukannya dalam konstruktor). Dan tentu saja Anda dapat menangani kesalahan alokasi di sana juga.


2

Hal besar tentang hilangnya pemeriksaan runtime dan crash adalah potensi bagi seorang hacker untuk menggunakan crash untuk mengambil alih proses dan mungkin kemudian mesin.

Sekarang, seorang hacker tidak dapat mengeksploitasi crash yang sebenarnya, segera setelah crash terjadi prosesnya tidak berguna dan tidak berbahaya. Jika Anda hanya membaca dari pointer nol itu adalah crash bersih, Anda membuat kesalahan dan proses mati seketika.

Bahaya datang ketika Anda menjalankan perintah yang secara teknis tidak ilegal, tetapi bukan apa yang Anda ingin lakukan, maka peretas mungkin bisa mengarahkan tindakan itu menggunakan data yang dibuat dengan hati-hati yang seharusnya aman.

Pengecualian tanpa tertangkap sebenarnya bukan crash nyata, itu hanya cara untuk mengakhiri program. Pengecualian hanya terjadi karena seseorang menulis perpustakaan yang secara khusus melempar pengecualian mengingat keadaan tertentu. Biasanya untuk menghindari masuk ke situasi yang tidak terduga, yang mungkin menjadi celah bagi seorang hacker.

Sebenarnya, langkah berbahaya adalah menangkap pengecualian daripada membiarkannya tergelincir, bukan berarti Anda tidak boleh melakukannya, tetapi pastikan bahwa Anda hanya menangkap orang-orang yang Anda tahu dapat Anda tangani dan biarkan sisanya tergelincir. Kalau tidak, Anda berisiko membatalkan tindakan pengamanan yang dilakukan dengan hati-hati di perpustakaan Anda.

Fokus pada kesalahan yang tidak perlu membuang pengecualian, seperti menulis di luar akhir array. Program Anda tidak akan mampu melakukannya?


2

Anda perlu melihat pengecualian Anda dan perlu melihat kondisi yang dapat memicu mereka. Banyak sekali waktu, sebagian besar dari apa yang digunakan orang untuk pengecualian dapat dihindari dengan pemeriksaan yang tepat selama siklus pengembangan / debug membangun / pengujian. Gunakan menegaskan, melewati referensi, selalu menginisialisasi variabel lokal pada deklarasi, memset-nol semua alokasi, NULL setelah gratis, dll, dan banyak sekali kasus pengecualian teoritis hanya pergi.

Pertimbangkan: jika sesuatu gagal dan mungkin menjadi kandidat untuk melemparkan pengecualian - apa dampaknya? Seberapa penting? Jika Anda gagal alokasi memori dalam permainan (untuk mengambil contoh asli Anda), Anda biasanya memiliki masalah yang lebih besar daripada bagaimana Anda menangani kasus kegagalan, dan menangani kasus kegagalan dengan pengecualian tidak akan membuat masalah yang lebih besar pergi. Lebih buruk - itu mungkin sebenarnya menyembunyikan fakta bahwa masalah yang lebih besar bahkan ada. Apakah kamu menginginkan itu? Saya tahu saya tidak. Saya tahu bahwa jika saya gagal alokasi memori saya ingin crash dan membakar mengerikan dan melakukannya sedekat mungkin dengan titik kegagalan, sehingga saya bisa masuk ke debugger, mencari tahu apa yang terjadi, dan memperbaikinya dengan benar.


1

Tidak yakin apakah mengutip orang lain sesuai untuk SE tetapi saya merasa tidak ada jawaban lain yang benar-benar menyentuh ini.

Mengutip Jason Gregory dari bukunya Game Engine Architecture . (BUKU HEBAT!)

"Penanganan pengecualian terstruktur (SEH) menambahkan banyak overhead ke program. Setiap bingkai tumpukan harus ditambah untuk mengandung informasi tambahan yang diperlukan oleh proses pelepasan tumpukan. Selain itu, pelonggaran tumpukan biasanya sangat lambat - pada urutan dua hingga tiga kali lebih mahal daripada hanya kembali dari fungsi. Juga, jika bahkan satu fungsi dalam program Anda (atau pustaka) menggunakan SEH, seluruh program Anda harus menggunakan SEH. Kompilator tidak dapat mengetahui fungsi mana yang mungkin berada di atas Anda pada tumpukan panggilan saat Anda melempar pengecualian. "

Dengan itu, mesin yang saya buat tidak menggunakan pengecualian. Ini karena potensi biaya kinerja yang disebutkan sebelumnya dan fakta bahwa untuk semua tujuan saya kode pengembalian kesalahan berfungsi dengan baik. Satu-satunya waktu saya benar-benar perlu mencari kegagalan adalah dalam menginisialisasi dan memuat sumber daya dan dalam kasus itu mengembalikan boolean yang menunjukkan keberhasilan bekerja dengan baik.


2
Penanganan pengecualian terstruktur adalah jenis penanganan pengecualian khusus yang tersedia di Windows, yang tidak sama dengan pengecualian C ++ pada umumnya.
Kylotan

Doh, aku tidak percaya aku tidak tahu ini. Terima kasih atas koreksinya!
KlashnikovKid

0

Saya selalu membuat pengecualian kode saya aman, hanya karena membuatnya lebih mudah untuk mengetahui di mana masalah muncul, karena saya dapat mencatat sesuatu setiap kali pengecualian dihasilkan atau ditangkap.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.