Untuk menambah jawaban di sini, saya pikir ada baiknya mempertimbangkan pertanyaan yang berlawanan dalam hubungannya dengan ini, yaitu. mengapa C membiarkan jatuh-di tempat pertama?
Setiap bahasa pemrograman tentu saja melayani dua tujuan:
- Berikan instruksi ke komputer.
- Tinggalkan catatan niat si programmer.
Karena itu, penciptaan bahasa pemrograman apa pun merupakan keseimbangan antara cara terbaik untuk melayani dua tujuan ini. Di satu sisi, semakin mudah untuk berubah menjadi instruksi komputer (apakah itu kode mesin, bytecode seperti IL, atau instruksi ditafsirkan pada eksekusi) maka lebih mampu bahwa proses kompilasi atau interpretasi akan menjadi efisien, dapat diandalkan dan kompak dalam keluaran. Diambil secara ekstrem, tujuan ini menghasilkan penulisan kami yang tepat di assembly, IL, atau bahkan op-code mentah, karena kompilasi termudah adalah di mana tidak ada kompilasi sama sekali.
Sebaliknya, semakin banyak bahasa yang mengekspresikan niat programmer, daripada sarana yang digunakan untuk tujuan itu, semakin dimengerti program baik saat menulis maupun selama pemeliharaan.
Sekarang, switch
selalu bisa dikompilasi dengan mengubahnya menjadi rantai if-else
blok yang setara atau serupa, tetapi ia dirancang sebagai memungkinkan kompilasi menjadi pola perakitan umum tertentu di mana seseorang mengambil nilai, menghitung offset dari itu (apakah dengan melihat sebuah tabel diindeks oleh hash sempurna dari nilai, atau dengan aritmatika aktual pada nilai *). Perlu dicatat pada titik ini bahwa hari ini, kompilasi C # kadang-kadang akan berubah switch
menjadi setara if-else
, dan kadang-kadang menggunakan pendekatan lompat berbasis hash (dan juga dengan C, C ++, dan bahasa lain dengan sintaks yang sebanding).
Dalam hal ini ada dua alasan bagus untuk membiarkan jatuh:
Itu terjadi secara alami: jika Anda membangun tabel lompatan ke dalam satu set instruksi, dan salah satu dari batch instruksi sebelumnya tidak mengandung semacam lompatan atau pengembalian, maka eksekusi akan secara alami berkembang ke batch berikutnya. Mengizinkan fall-through adalah apa yang akan "terjadi begitu saja" jika Anda mengubah switch
-menggunakan C-tabel-menggunakan kode mesin.
Coder yang menulis dalam assembly sudah terbiasa dengan yang setara: ketika menulis tabel lompat dengan tangan di assembly, mereka harus mempertimbangkan apakah suatu blok kode tertentu akan berakhir dengan return, lompatan di luar tabel, atau hanya melanjutkan ke blok berikutnya. Karena itu, meminta pembuat kode untuk menambahkan secara eksplisit break
jika diperlukan juga "alami" untuk pembuat kode.
Oleh karena itu, pada saat itu, merupakan upaya yang masuk akal untuk menyeimbangkan dua tujuan bahasa komputer karena berkaitan dengan kode mesin yang diproduksi, dan ekspresi kode sumber.
Namun, empat dekade kemudian, segalanya tidak persis sama, karena beberapa alasan:
- Coder di C hari ini mungkin memiliki sedikit atau tidak ada pengalaman perakitan. Coder dalam banyak bahasa gaya C lainnya bahkan lebih kecil kemungkinannya (terutama Javascript!). Konsep "apa yang digunakan orang dari majelis" tidak lagi relevan.
- Peningkatan dalam optimisasi berarti bahwa kemungkinan
switch
menjadi berubah if-else
karena dianggap pendekatan yang paling efisien, atau berubah menjadi varian esoterik khusus dari pendekatan jump-table yang lebih tinggi. Pemetaan antara pendekatan tingkat tinggi dan rendah tidak sekuat dulu.
- Pengalaman telah menunjukkan bahwa fall-through cenderung menjadi kasus minoritas daripada norma (sebuah studi kompiler Sun menemukan 3% dari
switch
blok menggunakan fall-through selain beberapa label pada blok yang sama, dan dianggap bahwa penggunaan kasus di sini berarti bahwa 3% ini sebenarnya jauh lebih tinggi dari biasanya). Jadi bahasa yang dipelajari membuat yang tidak biasa lebih siap melayani daripada yang umum.
- Pengalaman menunjukkan bahwa fall-through cenderung menjadi sumber masalah baik dalam kasus-kasus di mana hal itu secara tidak sengaja dilakukan, dan juga dalam kasus-kasus di mana fall-through yang benar terlewatkan oleh seseorang yang menjaga kode. Yang terakhir ini merupakan tambahan halus untuk bug yang terkait dengan fall-through, karena meskipun kode Anda bebas bug, fall-through Anda masih dapat menyebabkan masalah.
Terkait dengan dua poin terakhir, pertimbangkan kutipan berikut dari edisi K&R saat ini:
Jatuh dari satu kasus ke kasus lain tidak kuat, rentan terhadap disintegrasi ketika program dimodifikasi. Dengan pengecualian beberapa label untuk perhitungan tunggal, fall-throughs harus digunakan dengan hemat, dan dikomentari.
Sebagai bentuk yang baik, beri istirahat setelah kasus terakhir (default di sini) meskipun secara logis tidak perlu. Suatu hari ketika kasus lain ditambahkan di akhir, program defensif ini akan menyelamatkan Anda.
Jadi, dari mulut kuda, jatuh dalam C adalah masalah. Ini dianggap praktik yang baik untuk selalu mendokumentasikan kegagalan dengan komentar, yang merupakan penerapan prinsip umum bahwa seseorang harus mendokumentasikan di mana seseorang melakukan sesuatu yang tidak biasa, karena itulah yang akan menyebabkan pemeriksaan kode kemudian dan / atau membuat kode Anda terlihat seperti itu. memiliki bug pemula di dalamnya padahal sebenarnya benar.
Dan ketika Anda memikirkannya, kode seperti ini:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Adalah menambahkan sesuatu untuk membuat fall-through eksplisit dalam kode, itu bukan sesuatu yang dapat dideteksi (atau yang ketidakhadirannya dapat dideteksi) oleh kompiler.
Dengan demikian, fakta bahwa on harus eksplisit dengan fall-through dalam C # tidak menambah hukuman bagi orang yang menulis dengan baik dalam bahasa gaya C lainnya, karena mereka sudah akan eksplisit dalam fall-throughs mereka. †
Akhirnya, penggunaan di goto
sini sudah menjadi norma dari C dan bahasa lain seperti:
switch(x)
{
case 0:
case 1:
case 2:
foo();
goto below_six;
case 3:
bar();
goto below_six;
case 4:
baz();
/* FALLTHRU */
case 5:
below_six:
qux();
break;
default:
quux();
}
Dalam kasus seperti ini di mana kita ingin sebuah blok dimasukkan dalam kode yang dieksekusi untuk nilai selain hanya yang membawa satu ke blok sebelumnya, maka kita sudah harus menggunakan goto
. (Tentu saja, ada cara dan cara untuk menghindarinya dengan persyaratan yang berbeda tetapi itu berlaku untuk hampir semua yang berkaitan dengan pertanyaan ini). Karena itu, C # dibangun dengan cara yang sudah biasa untuk menangani satu situasi di mana kami ingin menekan lebih dari satu blok kode dalam a switch
, dan hanya menggeneralisasikannya untuk mencakup fall-through juga. Itu juga membuat kedua kasus lebih nyaman dan mendokumentasikan diri sendiri, karena kita harus menambahkan label baru di C tetapi dapat menggunakan case
sebagai label di C #. Di C # kita bisa menghilangkan below_six
label dan menggunakan goto case 5
yang lebih jelas tentang apa yang kita lakukan. (Kami juga harus menambahkanbreak
untuk default
, yang saya tinggalkan hanya untuk membuat kode C di atas jelas bukan kode C #).
Singkatnya karena itu:
- C # tidak lagi berkaitan dengan output kompiler yang tidak dioptimalkan secara langsung seperti kode C lakukan 40 tahun yang lalu (juga tidak C hari ini), yang membuat salah satu inspirasi dari jatuh-melalui menjadi tidak relevan.
- C # tetap kompatibel dengan C tidak hanya memiliki implisit
break
, untuk lebih mudah belajar bahasa oleh mereka yang akrab dengan bahasa yang sama, dan porting lebih mudah.
- C # menghapus kemungkinan sumber bug atau kode disalahpahami yang telah didokumentasikan dengan baik sebagai penyebab masalah selama empat dekade terakhir.
- C # membuat praktik terbaik yang ada dengan C (dokumen jatuh melalui) dapat diberlakukan oleh kompiler.
- C # membuat kasus yang tidak biasa satu dengan kode yang lebih eksplisit, kasus yang biasa satu dengan kode yang baru saja ditulis secara otomatis.
- C # menggunakan
goto
pendekatan berbasis yang sama untuk memukul blok yang sama dari yang berbedacase
label yang seperti yang digunakan dalam C. Itu hanya menggeneralisasikannya untuk beberapa kasus lain.
- C # membuat
goto
pendekatan berbasis itu lebih nyaman, dan lebih jelas, daripada di C, dengan memungkinkan case
pernyataan untuk bertindak sebagai label.
Semua dalam semua, keputusan desain yang cukup masuk akal
* Beberapa bentuk BASIC akan memungkinkan seseorang untuk melakukan hal-hal seperti GOTO (x AND 7) * 50 + 240
yang sementara rapuh dan karenanya kasus persuasif khusus untuk pelarangan goto
, memang berfungsi untuk menunjukkan setara bahasa yang lebih tinggi dari jenis cara kode tingkat yang lebih rendah dapat membuat lompatan berdasarkan aritmatika pada nilai, yang jauh lebih masuk akal ketika itu adalah hasil kompilasi daripada sesuatu yang harus dipertahankan secara manual. Implementasi Perangkat Duff khususnya cocok untuk kode mesin yang setara atau IL karena setiap blok instruksi akan sering memiliki panjang yang sama tanpa perlu penambahan nop
pengisi.
† Perangkat Duff muncul lagi di sini, sebagai pengecualian yang masuk akal. Fakta bahwa dengan pola itu dan yang serupa ada pengulangan operasi berfungsi untuk membuat penggunaan fall-through relatif jelas bahkan tanpa komentar eksplisit untuk efek itu.