Mengapa pernyataan peralihan dirancang untuk membutuhkan istirahat?

139

Diberikan pernyataan beralih sederhana

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

Tidak adanya pernyataan istirahat dalam kasus 2, menyiratkan bahwa eksekusi akan berlanjut di dalam kode untuk kasus 3. Ini bukan kecelakaan; itu dirancang seperti itu. Mengapa keputusan ini dibuat? Apa manfaatnya dari ini vs memiliki semantic break otomatis untuk blok? Apa alasannya?

EvilTeach
sumber

Jawaban:

150

Banyak jawaban tampaknya berfokus pada kemampuan untuk jatuh sebagai alasan untuk membutuhkannyabreak pernyataan itu.

Saya percaya itu hanya kesalahan, karena sebagian besar karena ketika C dirancang, tidak ada banyak pengalaman dengan bagaimana konstruksi ini akan digunakan.

Peter Van der Linden membuat kasus ini dalam bukunya "Expert C Programming":

Kami menganalisis sumber kompiler Sun C untuk melihat seberapa sering jatuh standar digunakan. Ujung depan kompilator Sun ANSI C memiliki 244 pernyataan switch, yang masing-masing memiliki rata-rata tujuh kasus. Kejatuhan terjadi hanya dalam 3% dari semua kasus ini.

Dengan kata lain, perilaku saklar normal salah 97% dari waktu. Bukan hanya di kompiler - sebaliknya, di mana fall through digunakan dalam analisis ini, itu sering untuk situasi yang terjadi lebih sering di kompiler daripada di perangkat lunak lain, misalnya, ketika mengkompilasi operator yang dapat memiliki satu atau dua operan :

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

Kasus jatuh begitu banyak diakui sebagai cacat sehingga bahkan ada konvensi komentar khusus, yang ditunjukkan di atas, yang memberitahu serat "ini benar-benar salah satu dari 3% kasus di mana jatuh diinginkan."

Saya pikir itu ide yang baik untuk C # untuk meminta pernyataan lompatan eksplisit di akhir setiap blok kasus (sementara masih memungkinkan beberapa label kasus ditumpuk - selama hanya ada satu blok pernyataan). Dalam C # Anda masih dapat memiliki satu kasus jatuh ke yang lain - Anda hanya perlu membuat jatuh melalui eksplisit dengan melompat ke kasus berikutnya menggunakangoto .

Sayang sekali Java tidak mengambil kesempatan untuk istirahat dari semantik C.

Michael Burr
sumber
Memang, saya pikir mereka pergi untuk kesederhanaan implementasi. Beberapa bahasa lain mendukung kasus yang lebih canggih (rentang, beberapa nilai, string ...) dengan biaya, mungkin, efisiensi.
PhiLho
Jawa mungkin tidak ingin menghentikan kebiasaan dan menyebarkan kebingungan. Untuk perilaku yang berbeda, mereka harus menggunakan semantik yang berbeda. Desainer Java kehilangan sejumlah peluang untuk keluar dari C.
PhiLho
@ Philho - Saya pikir Anda mungkin paling dekat dengan kebenaran dengan "kesederhanaan implementasi".
Michael Burr
Jika Anda menggunakan lompatan eksplisit melalui GOTO, bukankah sama produktifnya dengan hanya menggunakan serangkaian pernyataan IF?
DevinB
3
bahkan Pascal menerapkan saklar mereka tanpa putus. bagaimana mungkin C compiler-coder tidak memikirkannya @@
Chan Le
30

Dalam banyak hal c hanyalah antarmuka yang bersih untuk idiom perakitan standar. Ketika menulis tabel kontrol aliran didorong lompat, programmer memiliki pilihan jatuh melalui atau melompat keluar dari "struktur kontrol", dan lompat keluar membutuhkan instruksi eksplisit.

Jadi, c melakukan hal yang sama ...

dmckee --- mantan kucing moderator
sumber
1
Saya tahu banyak orang mengatakan itu, tetapi saya pikir itu bukan gambaran lengkap; C juga sering merupakan antarmuka yang jelek untuk perakitan antipatterns. 1. Jelek: Pernyataan alih-alih ekspresi. "Deklarasi mencerminkan penggunaan." Dimuliakan copy-paste sebagai yang mekanisme untuk modularitas dan portabilitas. Jenis sistem cara terlalu rumit; mendorong bug. NULL. (Lanjutan dalam komentar berikutnya.)
Harrison
2. Antipatterns: Tidak menyediakan serikat yang ditandai "bersih", salah satu dari dua idiom perwakilan representasi data yang mendasar. (Knuth, vol 1, ch 2) Alih-alih, "serikat pekerja yang tidak ditandai," peretasan non-idiomatis. Pilihan ini telah terpincang-pincang cara orang berpikir tentang data selama beberapa dekade. Dan NULstring yang dimusnahkan adalah tentang ide terburuk yang pernah ada.
Harrison
@ HarrisonKlaperman: Tidak ada metode untuk menyimpan string yang sempurna. Sebagian besar masalah yang terkait dengan string yang diakhiri null tidak akan terjadi jika rutinitas yang menerima string juga menerima parameter panjang maksimum, dan akan terjadi dengan rutinitas yang menyimpan string bertanda panjang ke buffer berukuran tetap tanpa menerima parameter ukuran buffer . Desain dari pernyataan switch di mana case hanya label mungkin tampak aneh bagi mata modern, tapi itu tidak lebih buruk dari desain DOloop Fortran .
supercat
Jika saya memutuskan untuk menulis tabel lompatan dalam perakitan, saya akan mengambil nilai case, dan secara ajaib mengubahnya menjadi subscript tabel lompat, lompat ke lokasi itu, dan jalankan kode. Setelah itu saya tidak akan terjun ke case selanjutnya. Saya akan melompat ke alamat seragam yang merupakan pintu keluar dari semua kasus. Gagasan bahwa saya akan melompat atau jatuh ke dalam tubuh kasus berikutnya adalah konyol. Tidak ada kasus penggunaan untuk pola itu.
EvilTeach
2
Sementara saat ini orang lebih banyak digunakan untuk membersihkan dan melindungi idola dan fitur bahasa yang mencegah diri dari menembak ke kaki, ini adalah peremajaan dari daerah di mana byte telah mahal (C dimulai sebelum 1970). jika kode Anda harus sesuai dengan 1024 byte, Anda akan mengalami tekanan berat untuk menggunakan kembali fragmen kode. Menggunakan kembali kode dengan mulai dari titik masuk yang berbeda dan berbagi tujuan yang sama adalah salah satu mekanisme untuk mencapai ini.
rpy
23

Untuk mengimplementasikan perangkat Duff, jelas:

dsend(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
FlySwat
sumber
3
Saya suka Perangkat Duff. Sangat elegan, dan cepat jahat.
dicroce
7
ya, tetapi apakah itu harus muncul setiap kali ada pernyataan beralih pada SO?
billjamesdev
Anda melewatkan dua kurung kurawal penutupan ;-).
Toon Krijthe
18

Jika case dirancang untuk pecah secara implisit maka Anda tidak bisa gagal.

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

Jika saklar dirancang untuk keluar secara implisit setelah setiap kasus Anda tidak akan punya pilihan tentang hal itu. Struktur sakelar didesain sedemikian rupa agar lebih fleksibel.

Bill the Lizard
sumber
12
Anda dapat membayangkan switch yang rusak secara implisit, tetapi memiliki kata kunci "fallthrough". Canggung, tapi bisa diterapkan.
dmckee --- ex-moderator kitten
Bagaimana itu lebih baik? Saya membayangkan pernyataan kasus bekerja dengan cara ini lebih sering daripada "blok kode per kasus" ... itu adalah blok if / then / else.
billjamesdev
Saya akan menambahkan bahwa dalam pertanyaan, lampiran lingkup {} di setiap blok kasus menambah kebingungan karena terlihat seperti gaya pernyataan "sementara".
Matthew Smith
1
@ Bill: Akan lebih buruk, saya pikir, tapi itu akan mengatasi keluhan yang dibawa oleh Mike B: bahwa jatuh-meskipun (selain beberapa kasus yang sama) adalah peristiwa langka dan tidak boleh menjadi perilaku default.
dmckee --- ex-moderator kitten
1
Saya meninggalkan kawat gigi untuk singkatnya, dengan beberapa pernyataan mereka harus ada di sana. Saya setuju bahwa kesalahan harus digunakan hanya ketika kasus persis sama. Sangat membingungkan ketika kasus dibangun di atas kasus sebelumnya menggunakan fallthrough.
Bill the Lizard
15

Pernyataan kasus dalam pernyataan switch hanyalah label.

Ketika Anda mengaktifkan nilai, pernyataan switch pada dasarnya melakukan kebohongan ke label dengan nilai yang cocok.

Ini berarti bahwa istirahat diperlukan untuk menghindari melewati kode di bawah label berikutnya.

Adapun alasan mengapa itu diterapkan dengan cara ini - sifat fall-through dari pernyataan switch dapat berguna dalam beberapa skenario. Sebagai contoh:

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;
LeopardSkinPillBoxHat
sumber
2
Ada dua masalah besar: 1) Melupakan di breakmana dibutuhkan. 2) Jika pernyataan kasus diubah pesanannya, fallthrough dapat mengakibatkan kasus yang salah dijalankan. Jadi, saya menemukan penanganan C # jauh lebih baik (eksplisit goto caseuntuk fallthrough, kecuali untuk label case kosong).
Daniel Rose
@DanielRose: 1) ada beberapa cara untuk melupakan breakdi C # juga - yang paling sederhana adalah ketika Anda tidak ingin casemelakukan apa pun kecuali lupa untuk menambahkan break(mungkin Anda begitu sibuk dengan komentar penjelasan, atau dipanggil untuk beberapa tugas lain): eksekusi hanya akan jatuh ke casebawah. 2) mendorong goto caseadalah mendorong pengkodean "spageti" yang tidak terstruktur - Anda dapat berakhir dengan siklus yang tidak disengaja ( case A: ... goto case B; case B: ... ; goto case A;), terutama ketika kasing dipisahkan dalam file dan sulit untuk digabung dalam kombinasi. Dalam C ++, fall-through dilokalkan.
Tony Delroy
8

Untuk mengizinkan hal-hal seperti:

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

Pikirkan casekata kunci sebagai gotolabel dan itu jauh lebih alami.

R .. GitHub BERHENTI MEMBANTU ICE
sumber
8
Bahwa if(0)pada akhir kasus pertama adalah kejahatan, dan hanya boleh digunakan jika tujuannya adalah untuk mengaburkan kode.
Daniel Rose
11
Seluruh jawaban ini adalah latihan untuk menjadi jahat, saya pikir. :-)
R .. GitHub BERHENTI MEMBANTU ICE
3

Ini menghilangkan duplikasi kode ketika beberapa kasus perlu mengeksekusi kode yang sama (atau kode yang sama secara berurutan).

Karena pada tingkat bahasa assembly, tidak masalah apakah Anda menerobos masing-masing atau tidak ada nol overhead untuk kasus jatuh, jadi mengapa tidak mengizinkan mereka karena mereka menawarkan keuntungan yang signifikan dalam kasus-kasus tertentu.

Greg Rogers
sumber
2

Saya kebetulan berlari ke dalam kasus penetapan nilai dalam vektor ke struct: itu harus dilakukan sedemikian rupa sehingga jika vektor data lebih pendek dari jumlah anggota data dalam struct, sisa anggota akan tetap di nilai default mereka. Dalam hal menghilangkan breakitu cukup berguna.

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}
Martti K
sumber
0

Seperti yang ditentukan di sini, ini memungkinkan satu blok kode berfungsi untuk banyak kasus. Ini seharusnya menjadi kejadian yang lebih umum untuk pernyataan switch Anda daripada "blok kode per kasus" yang Anda tentukan dalam contoh Anda.

Jika Anda memiliki blok kode per kasus tanpa jatuh, mungkin Anda harus mempertimbangkan untuk menggunakan blok if-elseif-else, karena itu akan tampak lebih tepat.

billjamesdev
sumber