Pertimbangkan pernyataan enum dan switch berikut:
typedef enum {
MaskValueUno,
MaskValueDos
} testingMask;
void myFunction(testingMask theMask) {
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
};
Saya seorang programmer Objective-C, tetapi saya telah menulis ini dalam C murni untuk audiens yang lebih luas.
Dentang / LLVM 4.1 dengan -Weverything memperingatkan saya di baris default:
Label default dalam sakelar yang mencakup semua nilai enumerasi
Sekarang, saya bisa melihat mengapa ini ada: di dunia yang sempurna, satu-satunya nilai yang masuk dalam argumen theMask
akan berada di enum, jadi tidak ada standar yang diperlukan. Tetapi bagaimana jika beberapa hack datang dan melempar int yang tidak diinisialisasi ke dalam fungsi saya yang cantik? Fungsi saya akan disediakan sebagai setetes di perpustakaan, dan saya tidak punya kendali atas apa yang bisa masuk ke sana. Menggunakan default
adalah cara penanganan yang sangat rapi.
Mengapa para dewa LLVM menganggap perilaku ini tidak layak bagi perangkat infernal mereka? Haruskah saya mendahului ini dengan pernyataan if untuk memeriksa argumen?
sumber
"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode."
.-Weverything
bisa bermanfaat, tetapi hati-hati tentang mengubah kode Anda terlalu banyak untuk menghadapinya. Beberapa dari peringatan itu tidak hanya tidak berharga tetapi juga kontra-produktif, dan sebaiknya dimatikan. (Memang, itulah use case untuk-Weverything
: mulai dengan itu, dan matikan apa yang tidak masuk akal.)Jawaban:
Berikut adalah versi yang tidak mengalami masalah yang terkait dengan pelaporan, atau versi yang Anda jaga:
Killian telah menjelaskan mengapa dentang memancarkan peringatan: jika Anda memperpanjang enum, Anda akan jatuh ke dalam kasus default yang mungkin bukan yang Anda inginkan. Yang benar untuk dilakukan adalah menghapus case default dan mendapatkan peringatan untuk kondisi yang tidak tertangani .
Sekarang Anda khawatir seseorang dapat memanggil fungsi Anda dengan nilai di luar pencacahan. Kedengarannya seperti gagal memenuhi persyaratan fungsi: itu didokumentasikan untuk mengharapkan nilai dari
testingMask
enumerasi tetapi programmer telah melewati sesuatu yang lain. Jadi buatlah kesalahan programmer menggunakanassert()
(atauNSCAssert()
seperti yang Anda katakan Anda menggunakan Objective-C). Buat program Anda macet dengan pesan yang menjelaskan bahwa programmer salah melakukannya, jika programmer salah.sumber
return;
dan menambahkanassert(false);
sampai akhir (daripada mengulangi diri saya sendiri dengan mendaftar enum hukum di awalassert()
dan diswitch
).Memiliki
default
label di sini adalah indikator bahwa Anda bingung tentang apa yang Anda harapkan. Karena Anda telah kehabisan semuaenum
nilai yang mungkin secara eksplisit, maka nilaidefault
tersebut tidak dapat dieksekusi, dan Anda tidak memerlukannya untuk menjaga terhadap perubahan di masa mendatang, karena jika Anda memperpanjangenum
, maka konstruk tersebut sudah akan menghasilkan peringatan.Jadi, kompiler memperhatikan bahwa Anda telah membahas semua pangkalan tetapi tampaknya berpikir bahwa Anda belum melakukannya, dan itu selalu merupakan pertanda buruk. Dengan mengambil upaya minimal untuk mengubah
switch
ke bentuk yang diharapkan, Anda menunjukkan kepada kompiler bahwa apa yang tampaknya Anda lakukan adalah apa yang sebenarnya Anda lakukan, dan Anda tahu itu.sumber
Dentang bingung, memiliki pernyataan default ada praktik baik-baik saja, itu dikenal sebagai pemrograman defensif dan dianggap praktik pemrograman yang baik (1). Ini digunakan banyak dalam sistem mission-critical, meskipun mungkin tidak dalam pemrograman desktop.
Tujuan pemrograman defensif adalah untuk menangkap kesalahan tak terduga yang secara teori tidak akan pernah terjadi. Kesalahan yang tidak terduga seperti itu belum tentu programmer memberikan fungsi input yang salah, atau bahkan "peretas jahat". Kemungkinan besar, itu bisa disebabkan oleh variabel yang korup: buffer overflows, stack overflow, runaway code, dan bug serupa yang tidak terkait dengan fungsi Anda dapat menyebabkan hal ini. Dan dalam hal sistem tertanam, variabel mungkin dapat berubah karena EMI, terutama jika Anda menggunakan sirkuit RAM eksternal.
Adapun apa yang ditulis di dalam pernyataan default ... jika Anda mencurigai bahwa program tersebut telah rusak setelah Anda berakhir di sana, maka Anda memerlukan semacam penanganan kesalahan. Dalam banyak kasus Anda mungkin hanya dapat menambahkan pernyataan kosong dengan komentar: "tak terduga tetapi tidak masalah" dll, untuk menunjukkan bahwa Anda telah memikirkan situasi yang tidak mungkin.
(1) MISRA-C: 2004 15.3.
sumber
-Weverything
menghidupkan setiap peringatan, bukan pilihan yang sesuai dengan kasus penggunaan tertentu. Tidak sesuai dengan selera Anda tidak sama dengan kebingungan.Lebih baik lagi:
Ini lebih rentan kesalahan saat menambahkan item ke enum. Anda dapat melewati tes untuk> = 0 jika Anda membuat nilai enum Anda tidak ditandatangani. Metode ini hanya berfungsi jika Anda tidak memiliki celah dalam nilai enum Anda, tetapi itu sering terjadi.
sumber
Kemudian Anda mendapatkan Perilaku Tidak Terdefinisi , dan Anda
default
akan menjadi tidak berarti. Tidak ada yang bisa Anda lakukan untuk menjadikan ini lebih baik.Biarkan saya lebih jelas. Instan seseorang meneruskan inisialisasi
int
ke fungsi Anda, itu adalah Perilaku Tidak Terdefinisi. Fungsi Anda dapat memecahkan Masalah Pemutusan dan tidak masalah. Itu adalah UB. Tidak ada yang dapat Anda lakukan setelah UB dipanggil.sumber
Pernyataan default tidak selalu membantu. Jika sakelar lebih dari enum, nilai apa pun yang tidak didefinisikan dalam enum akan berakhir menjalankan perilaku yang tidak ditentukan.
Untuk semua yang Anda tahu, kompiler dapat mengkompilasi saklar itu (dengan default) sebagai:
Setelah Anda memicu perilaku yang tidak terdefinisi, tidak ada jalan untuk kembali.
sumber
enum
tipe sama dengan rentang nilai dari tipe integer yang mendasarinya (yang didefinisikan oleh implementasi, karenanya harus konsisten sendiri).Saya juga lebih suka memiliki
default:
dalam semua kasus. Saya terlambat ke pesta seperti biasa, tapi ... beberapa pemikiran lain yang tidak saya lihat di atas:-Werror
) berasal dari-Wcovered-switch-default
(dari-Weverything
tetapi tidak-Wall
). Jika fleksibilitas moral Anda memungkinkan Anda mematikan peringatan tertentu (mis. Cukai beberapa hal dari-Wall
atau-Weverything
), pertimbangkan untuk melempar-Wno-covered-switch-default
(atau-Wno-error=covered-switch-default
ketika menggunakan-Werror
), dan secara umum-Wno-...
untuk peringatan lain yang menurut Anda tidak menyenangkan.gcc
(dan perilaku yang lebih generik diclang
), konsultasikangcc
manualnya untuk-Wswitch
,-Wswitch-enum
,-Wswitch-default
untuk (yang berbeda) perilaku dalam situasi yang sama dari jenis yang disebutkan dalam laporan beralih.Saya juga tidak suka peringatan ini dalam konsep, dan saya tidak suka kata-katanya; bagi saya, kata-kata dari peringatan ("label default ... mencakup semua ... nilai") menunjukkan bahwa
default:
case akan selalu dieksekusi, sepertiPada pembacaan pertama, ini adalah apa yang saya pikir Anda berjalan ke - bahwa Anda
default:
kasus akan selalu dieksekusi karena tidak adabreak;
ataureturn;
atau serupa. Konsep ini mirip (dengan telingaku) dengan komentar gaya pengasuh lainnya (meskipun kadang-kadang bermanfaat) yang muncul dariclang
. Jikafoo == 1
, kedua fungsi akan dieksekusi; kode Anda di atas memiliki perilaku ini. Yaitu, gagal break-out hanya jika Anda ingin tetap menjaga mengeksekusi kode dari kasus-kasus berikutnya! Namun, ini tampaknya bukan masalah Anda.Dengan risiko menjadi sombong, beberapa pemikiran lain untuk kelengkapan:
int
atau sesuatu ke fungsi ini, yang secara eksplisit berniat untuk mengkonsumsi tipe spesifik Anda sendiri, kompiler Anda harus melindungi Anda sama baiknya dalam situasi itu dengan peringatan atau kesalahan yang agresif. TAPI tidak! (Yaitu, tampaknya paling tidakgcc
danclang
tidak melakukanenum
pengecekan tipe, tapi saya dengaricc
tidak .) Karena Anda tidak mendapatkan tipe -Safety, Anda bisa mendapatkan nilai -Safety seperti dibahas di atas. Kalau tidak, seperti yang disarankan dalam TFA, pertimbangkan suatustruct
atau sesuatu yang dapat memberikan keamanan jenis.enum
sepertiMaskValueIllegal
, dan tidak mendukung itucase
di Andaswitch
. Itu akan dimakan olehdefault:
(selain nilai aneh lainnya)Pengodean bertahan lama!
sumber
Berikut adalah saran alternatif:
OP berusaha melindungi terhadap kasus di mana seseorang lewat di
int
mana enum diharapkan. Atau, lebih mungkin, di mana seseorang telah menautkan pustaka lama dengan program yang lebih baru menggunakan header yang lebih baru dengan lebih banyak case.Mengapa tidak mengganti sakelar untuk menangani
int
kasing? Menambahkan gips di depan nilai dalam switch menghilangkan peringatan dan bahkan memberikan sedikit petunjuk tentang mengapa default ada.Saya menemukan ini jauh lebih tidak menyenangkan daripada
assert()
menguji masing-masing nilai yang mungkin atau bahkan membuat asumsi bahwa kisaran nilai enum disusun dengan baik sehingga tes yang lebih sederhana bekerja. Ini hanyalah cara jelek untuk melakukan apa yang default lakukan dengan tepat dan indah.sumber