Mengapa kebotakan berbahaya?
goto
tidak menyebabkan ketidakstabilan dengan sendirinya. Meskipun sekitar 100.000 goto
detik, kernel Linux masih menjadi model stabilitas.
goto
dengan sendirinya seharusnya tidak menyebabkan kerentanan keamanan. Namun dalam beberapa bahasa, mencampurnya dengan try
/ catch
blok manajemen pengecualian dapat menyebabkan kerentanan seperti yang dijelaskan dalam rekomendasi CERT ini . Kompilator Mainstream menandai flag dan mencegah kesalahan seperti itu, tetapi sayangnya, kompiler yang lebih lama atau lebih eksotis tidak.
goto
menyebabkan kode tidak terbaca dan tidak dapat dipelihara. Ini juga disebut kode spageti , karena, seperti pada piring spageti, sangat sulit untuk mengikuti aliran kontrol ketika ada terlalu banyak foto.
Bahkan jika Anda berhasil menghindari kode spageti dan jika Anda hanya menggunakan beberapa foto, mereka masih memfasilitasi bug seperti dan sumber daya bocor:
- Kode yang menggunakan pemrograman struktur, dengan blok dan loop bersarang yang jelas, mudah diikuti; alur kontrolnya sangat mudah ditebak. Karena itu lebih mudah untuk memastikan bahwa invarian dihormati.
- Dengan sebuah
goto
pernyataan, Anda mematahkan aliran langsung itu, dan mematahkan harapan. Misalnya, Anda mungkin tidak menyadari bahwa Anda masih harus membebaskan sumber daya.
- Banyak
goto
tempat yang berbeda dapat mengirim Anda ke satu target goto. Jadi tidak jelas untuk mengetahui dengan pasti keadaan Anda saat mencapai tempat ini. Risiko membuat asumsi yang salah / tidak berdasar karenanya cukup besar.
Informasi dan kutipan tambahan:
C memberikan goto
pernyataan dan label yang tak dapat disalahgunakan yang tak terbatas untuk dicabuti . Secara formal goto
tidak pernah diperlukan, dan dalam praktiknya hampir selalu mudah untuk menulis kode tanpanya. (...)
Meskipun demikian kami akan menyarankan beberapa situasi di mana goto dapat menemukan tempat. Penggunaan yang paling umum adalah meninggalkan pemrosesan dalam beberapa struktur yang bersarang secara mendalam, seperti memecah dua loop sekaligus. (...)
Meskipun kami tidak dogmatis tentang masalah ini, tampaknya pernyataan goto harus digunakan dengan hemat, jika memang ada .
Kapan bisa digunakan?
Seperti K&R, saya tidak dogmatis tentang goto. Saya akui bahwa ada situasi di mana kebersamaan bisa memudahkan hidup seseorang.
Biasanya, dalam C, goto memungkinkan keluar loop bertingkat, atau penanganan kesalahan yang diperlukan untuk mencapai titik keluar yang sesuai yang membebaskan / membuka semua sumber daya yang dialokasikan sejauh ini (alokasi beberapa item secara berurutan berarti beberapa label). Artikel ini mengukur berbagai penggunaan goto di kernel Linux.
Secara pribadi saya lebih suka menghindarinya dan dalam 10 tahun C, saya menggunakan maksimal 10 foto. Saya lebih suka menggunakan nested if
s, yang menurut saya lebih mudah dibaca. Ketika ini akan menyebabkan sarang terlalu dalam, saya akan memilih untuk menguraikan fungsi saya di bagian yang lebih kecil, atau menggunakan indikator boolean dalam kaskade. Kompilator pengoptimal hari ini cukup pintar untuk menghasilkan kode yang hampir sama dengan kode yang sama goto
.
Penggunaan goto sangat tergantung pada bahasa:
Dalam C ++, penggunaan RAII yang tepat menyebabkan kompiler untuk secara otomatis menghancurkan objek yang keluar dari ruang lingkup, sehingga sumber daya / kunci tetap akan dibersihkan, dan tidak lagi membutuhkan goto.
Di Jawa tidak perlu untuk goto (lihat kutipan penulis Jawa di atas dan ini sangat baik jawaban Stack Overflow ): pengumpul sampah yang membersihkan kekacauan, break
, continue
, dan try
/ catch
penanganan eksepsi mencakup semua kasus di mana goto
bisa membantu, tetapi dalam lebih aman dan lebih baik cara. Popularitas Java membuktikan bahwa pernyataan goto dapat dihindari dalam bahasa modern.
Zoom pada goto SSL terkenal gagal kerentanan
Penafian Penting: mengingat diskusi yang sengit dalam komentar, saya ingin mengklarifikasi bahwa saya tidak berpura-pura bahwa pernyataan kebohongan adalah satu-satunya penyebab bug ini. Saya tidak berpura-pura bahwa tanpa goto tidak akan ada bug. Saya hanya ingin menunjukkan bahwa seorang goto dapat terlibat dalam bug serius.
Saya tidak tahu berapa banyak bug serius yang terkait goto
dalam sejarah pemrograman: detail sering tidak dikomunikasikan. Namun ada bug Apple SSL terkenal yang melemahkan keamanan iOS. Pernyataan yang menyebabkan bug ini adalah goto
pernyataan yang salah .
Beberapa berpendapat bahwa akar penyebab bug itu bukan pernyataan goto itu sendiri, tetapi salinan / tempel yang salah, lekukan menyesatkan, hilang kawat gigi keriting di sekitar blok bersyarat, atau mungkin kebiasaan kerja pengembang. Saya tidak dapat mengkonfirmasi salah satu dari mereka: semua argumen ini adalah hipotesis dan interpretasi yang memungkinkan. Tidak ada yang benar-benar tahu. ( Sementara itu, hipotesis gabungan yang salah seperti yang disarankan seseorang dalam komentar tampaknya menjadi kandidat yang sangat baik mengingat beberapa inkonsistensi lekukan lain dalam fungsi yang sama ).
Satu-satunya fakta obyektif adalah bahwa duplikat goto
menyebabkan keluar dari fungsi sebelum waktunya. Melihat kode tersebut, satu-satunya pernyataan tunggal yang dapat menyebabkan efek yang sama adalah pengembalian.
Kesalahan dalam fungsi SSLEncodeSignedServerKeyExchange()
dalam file ini :
if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
goto fail;
if ((err =...) !=0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail; // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
if (...)
goto fail;
... // Do some cryptographic operations here
fail:
... // Free resources to process error
Memang kurung kurawal di sekitar blok bersyarat bisa mencegah bug:
itu akan menyebabkan kesalahan sintaks saat kompilasi (dan karenanya koreksi) atau ke goto tidak berbahaya yang berlebihan. Omong-omong, GCC 6 akan dapat menemukan kesalahan ini berkat peringatan opsional untuk mendeteksi lekukan yang tidak konsisten.
Tapi pertama-tama, semua foto ini bisa dihindari dengan kode yang lebih terstruktur. Jadi goto setidaknya secara tidak langsung menjadi penyebab bug ini. Setidaknya ada dua cara berbeda yang bisa menghindarinya:
Pendekatan 1: jika klausa atau bersarang if
s
Alih-alih menguji banyak kondisi untuk kesalahan secara berurutan, dan setiap kali mengirim ke fail
label jika ada masalah, orang bisa memilih untuk mengeksekusi operasi kriptografi dalam if
pernyataan-yang hanya akan melakukannya jika tidak ada pra-kondisi yang salah:
if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
(err = ...) == 0 ) &&
(err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
...
(err = ...) == 0 ) )
{
... // Do some cryptographic operations here
}
... // Free resources
Pendekatan 2: gunakan akumulator kesalahan
Pendekatan ini didasarkan pada kenyataan bahwa hampir semua pernyataan di sini memanggil beberapa fungsi untuk menetapkan err
kode kesalahan, dan mengeksekusi sisa kode hanya jika err
0 (yaitu, fungsi dieksekusi tanpa kesalahan). Alternatif aman dan mudah dibaca adalah:
bool ok = true;
ok = ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok = ok && (err = NextFunction(...)) == 0;
...
ok = ok && (err = ...) == 0;
... // Free resources
Di sini, tidak ada goto tunggal: tidak ada risiko untuk melompat dengan cepat ke titik keluar kegagalan. Dan secara visual akan mudah menemukan garis yang tidak selaras atau terlupakan ok &&
.
Konstruk ini lebih kompak. Ini didasarkan pada fakta bahwa dalam C, bagian kedua dari logika dan ( &&
) dievaluasi hanya jika bagian pertama benar. Bahkan, assembler yang dihasilkan oleh kompiler pengoptimalisasi hampir setara dengan kode asli dengan gotos: Pengoptimal mendeteksi dengan sangat baik rantai kondisi dan menghasilkan kode, yang pada awalnya nilai pengembalian bukan nol melompat ke akhir ( bukti online ).
Anda bahkan bisa membayangkan pemeriksaan konsistensi di akhir fungsi yang bisa selama fase pengujian mengidentifikasi ketidakcocokan antara flag ok dan kode kesalahan.
assert( (ok==false && err!=0) || (ok==true && err==0) );
Kesalahan seperti itu secara ==0
tidak sengaja diganti dengan !=0
kesalahan konektor atau logis akan dengan mudah terlihat selama fase debugging.
Seperti yang dikatakan: Saya tidak berpura-pura bahwa konstruksi alternatif akan menghindari bug. Saya hanya ingin mengatakan bahwa mereka dapat membuat bug lebih sulit terjadi.