Anahtar kasasında geçerli, ama değersiz sözdizimi?


207

Küçük bir yazım hatasıyla, bu yapıyı yanlışlıkla buldum:

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

İfadenin printfüst kısmındaki switchgeçerli, ama aynı zamanda tamamen ulaşılamaz görünüyor.

Ulaşılamayan kod hakkında bile bir uyarı yapmadan temiz bir derleme aldım, ama bu anlamsız görünüyor.

Bir derleyici bunu erişilemez kod olarak işaretlemeli mi?
Bu herhangi bir amaca hizmet ediyor mu?


51
GCC'nin bunun için özel bir bayrağı var. Bu-Wswitch-unreachable
Eli Sadoff

57
"Bu herhangi bir amaca hizmet ediyor mu?" Pekala, gotoulaşılamayan bölüme girip çıkabilirsiniz, bu da çeşitli kesmek için yararlı olabilir.
HolyBlackCat

12
@HolyBlackCat Tüm ulaşılamaz kodlar için böyle olmaz mıydı?
Eli Sadoff

28
@EliSadoff Gerçekten. Sanırım özel bir amacı yok. Bahse girerim izin verilmez çünkü yasaklamak için bir sebep yoktur. Sonuçta switch, sadece gotobirden fazla etikete sahip bir şarttır . Gövdesi üzerinde, goto etiketleriyle dolu normal bir kod bloğunda olduğu gibi az çok aynı kısıtlamalar vardır.
HolyBlackCat

16
@MooingDuck ın örneğinin Duff'ın cihazında bir varyant olduğuna dikkat çekmeye değer ( en.wikipedia.org/wiki/Duff's_device )
Michael Anderson

Yanıtlar:


226

Belki de en yararlı değil, ama tamamen değersiz değil . switchKapsam içinde mevcut bir yerel değişkeni bildirmek için kullanabilirsiniz .

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

Standard ( N1579 6.8.4.2/7) aşağıdaki örneğe sahiptir:

ÖRNEK Yapay program parçasında

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}

tanımlayıcısı iotomatik depolama süresi (blok içinde) ile var olan ancak hiçbir zaman başlatılmayan nesne ve dolayısıyla kontrol ifadesi sıfırdan farklı bir değere sahipse, printfişleve çağrı belirsiz bir değere erişir. Benzer şekilde, fonksiyon çağrısına fulaşılamaz.

PS BTW, örnek geçerli C ++ kodu değil. Bu durumda ( N4140 6.7/3, benimkini vurgulayın):

Otomatik depolama süresine sahip bir değişkenin kapsamda olmadığı bir noktadan 90'ı , değişkenin skaler tipine , önemsiz bir varsayılan kurucuya sahip sınıf tipine ve önemsiz bir yıkıcıya sahip olmadıkça kapsamının yanlış olduğu bir noktaya atlayan bir program , bu türlerden birinin cv nitelikli bir sürümü veya önceki türlerden birinin dizisi ve bir başlatıcı (8.5) olmadan bildirilir .


90) Bir switchifadenin durumundan bir vaka etiketine transfer, bu açıdan bir sıçrama olarak değerlendirilir.

Değiştirilmesi Yani int i = 4;sahip int i;markaların geçerli bir C ++.


3
“... ama asla başlatılmıyor ...” Görünüşe göre i4'e başlatıldı, ne eksik?
yano

7
Değişken staticsıfırsa sıfırlanacağını unutmayın, bu nedenle bunun için de güvenli bir kullanım vardır.
Leushenko

23
@yano Her zaman i = 4;başlatmanın üzerine atlarız , bu yüzden asla gerçekleşmez.
AlexD

12
Tabii ki! ... sorunun tüm noktası ... Tanrım. Bu aptallığı silmek arzusu güçlü
yano

1
Güzel! Bazen a'nın içinde bir temp değişkenine ihtiyacım casevardı ve her zaman her birinde farklı adlar kullanmak caseveya anahtarın dışında tanımlamak zorunda kaldım .
SJuan76

98

Bu herhangi bir amaca hizmet ediyor mu?

Evet. Bir ifade yerine, ilk etiketin önüne bir bildirim koyarsanız, bu çok mantıklı olabilir:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

Beyanlar ve ifadeler için kurallar genel olarak bloklar için paylaşılır, bu yüzden orada da ifadelere izin veren kural aynıdır.


Ayrıca, ilk ifade bir döngü yapısı ise, vaka etiketlerinin döngü gövdesinde görünebileceğidir:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

Yazmanın daha okunabilir bir yolu varsa, lütfen böyle bir kod yazmayın, ancak mükemmel bir şekilde geçerlidir ve f()çağrıya ulaşılabilir.


@MatthieuM Duff'un Cihazı bir döngü içinde vaka etiketlerine sahiptir, ancak döngüden önce bir vaka etiketi ile başlar.

2
İlginç bir örnek için mi yoksa gerçek bir programda yazmanın çılgınlığı için mi aşağı oy vereceğimden emin değilim :). Uçuruma daltığınız ve tek parça halinde döndüğünüz için tebrikler.
Liviu T.

@ChemicalEngineer: Kod Duff'ın Cihazında olduğu gibi bir döngünün parçasıysa, { /*code*/ switch(x) { } }daha temiz görünebilir , ancak aynı zamanda yanlıştır .
Ben Voigt

39

Duff's Device adında ünlü bir kullanım alanı var .

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

Burada ile gösterilen bir arabelleği, ile gösterilen bir arabelleğe kopyalarız from.to . countVeri örneklerini kopyalarız .

do{}while()Deyimi ilk önce başlar caseetiket vecase etiket içinde gömülü do{}while().

Bu, sondaki koşullu dalların sayısını azaltır. do{}while() , kabaca 4 faktörünün karşılaştığı döngünün (bu örnekte, sabit istediğiniz herhangi bir değere ayarlanabilir).

Şimdi, optimize ediciler bazen bunu sizin için yapabilir (özellikle akış / vektörleştirilmiş talimatları optimize ediyorlarsa), ancak profil güdümlü optimizasyon olmadan, döngünün büyük olmasını isteyip istemediğinizi bilemezler.

Genel olarak, değişken bildirimler orada meydana gelebilir ve her durumda kullanılabilir, ancak anahtar sona erdikten sonra kapsam dışıdır. (başlatma işleminin atlanacağına dikkat edin)

Ayrıca, anahtara özgü olmayan kontrol akışı, yukarıda gösterildiği gibi veya a ile anahtar bloğunun o bölümüne girmenizi sağlayabilir goto.


2
Tabii ki, bu, ilk durumun üstündeki ifadelere izin vermeden, yine de, sırası do {ve case 0:önemi olmayan, her ikisinin de ilkine bir atlama hedefi koymaya hizmet etmesine rağmen mümkün olacaktır *to = *from++;.
Ben Voigt

1
@BenVoigt Ben koymak do {daha okunabilir olduğunu iddia ediyorum . Evet, Duff'ın Cihazı için okunabilirliği tartışmak aptalca ve anlamsız ve muhtemelen delirmenin basit bir yoludur.
Monica'nın Davası

1
@QPaysTaxes Simon Tatham adlı kişinin Coroutines kullanıcısına göz atın . Ya da olmayabilir.
Jonas Schäfer

@ JonasSchäfer Eğlenceli, temel olarak C ++ 20 yardımcı programlarının sizin için yapacağı şey budur.
Yakk - Adam Nevraumont

15

Linux'ta gcc kullandığınızı varsayarsak, 4.4 veya önceki bir sürümünü kullanıyorsanız size bir uyarı verirdi.

-Wunreachable-kod seçeneği gcc 4.4'ten itibaren kaldırılmıştır .


Sorunu ilk elden yaşamak her zaman yardımcı olur!
16ton

@JonathanLeffler: Seçilen belirli optimizasyon geçişleri kümesine duyarlı olan gcc uyarılarının genel sorunu maalesef hala doğrudur ve kötü bir kullanıcı deneyimi sağlar. /: Bir başarısız sürüm oluşturma ardından temiz bir hata ayıklama yapı olması gerçekten sinir bozucu
Matthieu M.

@MatthieuM .: Semantik analizden ziyade gramer içeren durumlarda böyle bir uyarının tespit edilmesi son derece kolay görünmektedir [örn. Kod, "her iki dalda da geri dönüşleri olan bir" if "ifadesini takip eder ve" rahatsızlığı "bastırmayı kolaylaştırır uyarılar. Öte yandan, hata ayıklama yapılarında bulunmayan sürüm yapılarında hata veya uyarı almanın yararlı olduğu bazı durumlar vardır (başka bir şey değilse, hata ayıklama için yerleştirilen bir saldırının temizlenmesi gereken yerlerde serbest bırakmak).
17'de supercat

1
@MatthieuM .: Belirli bir değişkenin kodun bir noktasında her zaman yanlış olacağını keşfetmek için önemli bir anlamsal analiz gerekirse, bu değişkenin doğru olması için koşullu olan koda sadece bu analiz yapıldıysa erişilemez olarak bulunur. Öte yandan, söz konusu kodun sözdizimsel olarak erişilemeyen kodla ilgili bir uyarıdan oldukça farklı bir şekilde ulaşılamadığını, çünkü bazı proje yapılandırmalarıyla çeşitli koşulların mümkün olması, ancak diğerlerinin mümkün olmaması tamamen normal bir uyarı olduğunu düşünürdüm . Programcılar için zaman zaman yardımcı olabilir ...
Supercat

1
... bazı yapılandırmaların neden diğerlerinden daha büyük kod oluşturduğunu bilmek [örneğin, bir derleyici bir durumu bir yapılandırmada imkansız olarak kabul edebilir, ancak başka bir şekilde değil], ancak bu, kodda optimize edilebilen "yanlış" bir şey olduğu anlamına gelmez. bu moda bazı konfigürasyonlarla.
17'de supercat

11

Sadece değişken bildirim için değil, ileri atlama için de. Sadece spagetti koduna eğilimli değilseniz ve iyi kullanabilirsiniz.

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

Baskılar

1
no case
0 /* Notice how "0" prints even though i = 1 */

Anahtar-kasanın en hızlı kontrol akış maddelerinden biri olduğuna dikkat edilmelidir. Bu nedenle, bazen bu gibi durumları içeren programcı için çok esnek olmalıdır.


Ve arasındaki fark nedir nocase:ve default:?
i486

@ i486 i=4Tetiklemediğinde nocase.
Sanchke Dellowar

@SanchkeDellowar demek istediğim bu.
njzk2

Neden bu, durum 1'i durum 0'dan önce basitçe yerine koymak ve geçişi kullanmak yerine yapsın?
Jonas Schäfer

@JonasWielicki Bu amaç için bunu yapabilirsiniz. Ancak bu kod, yapılabileceklerin sadece bir örneğidir.
Sanchke Dellowar

11

İfadedeki kodda switchveya case *:etiketlerin bu kodun içine yerleştirildiği yerde neredeyse hiçbir yapısal kısıtlamanın olmadığı not edilmelidir *. Bu, duff'un cihazı gibi programlama hilelerini mümkün kılar, olası bir uygulaması şöyle görünür:

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

Gördüğünüz gibi, switch(n%8) {ve case 7:etiketi arasındaki koda kesinlikle ulaşılabilir ...


* Supercat'in bir yorumda şükür ki işaret ettiği gibi: C99'dan beri, VLA bildirimi içeren bir bildirim kapsamında ne bir gotone de bir etiket ( case *:etiket olsun ya da olmasın) görünemez. Dolayısıyla , etiketlerin yerleştirilmesinde yapısal kısıtlamalar olmadığını söylemek doğru değildir case *:. Bununla birlikte, duff'un cihazı C99 standardından önce gelir ve yine de VLA'lara bağlı değildir. Yine de, bu yüzden ilk cümle içine "neredeyse" eklemek zorunda hissettim.


Değişken uzunlukta dizilerin eklenmesi, bunlarla ilgili yapısal kısıtlamaların uygulanmasına yol açmıştır.
supercat

@supercat Ne tür kısıtlamalar?
cmaster - eski haline monica

1
Değişken olarak beyan edilen herhangi bir nesne veya tür kapsamında ne bir gotone de switch/case/defaultetiket görünemez. Bu, bir blok değişken uzunluklu dizi nesnelerinin veya türlerinin herhangi bir bildirimini içeriyorsa, tüm etiketlerin bu bildirimlerden önce gelmesi gerektiği anlamına gelir. Standartta, bazı durumlarda bir VLA beyanının kapsamının bir anahtar ifadesinin tamamını kapsadığını düşündüren kafa karıştırıcı bir çelişki vardır; bu konudaki sorum için stackoverflow.com/questions/41752072/… adresine bakın .
17'de supercat

@supercat: Sadece söz konusu yanlışlığı yanlış anladınız (ki sanırım sorunuzu neden sildiniz). Bir VLA'nın tanımlanabileceği kapsam için bir gereklilik getirmektedir. Bu kapsamı genişletmez, sadece belirli VLA tanımlarını geçersiz kılar.
Keith Thompson

@ KeithThompson: Evet, yanlış anlamıştım. Dipnottaki mevcut zamanın garip kullanımı işleri kafa karıştırıcı hale getirdi ve bence kavram bir yasak olarak daha iyi ifade edilebilirdi: "Vücudu içinde bir VLA bildirimi içeren bir anahtar ifadesi, msgstr "% s VLA bildiriminin kapsamı".
supercat

10

Uyarıyı oluşturmak için gerekli gccseçenekle ilgili cevabınızı aldınız -Wswitch-unreachable, bu cevap kullanılabilirlik / değerlilik kısmı üzerinde durmaktır .

Doğrudan alıntı C11, bölüm §6.8.4.2, ( benimkini vurgula )

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}

tanımlayıcısı iotomatik depolama süresi (blok içinde) ile var olan ancak hiçbir zaman başlatılmayan nesne ve dolayısıyla kontrol ifadesi sıfırdan farklı bir değere sahipse, printf işleve çağrı belirsiz bir değere erişir. Benzer şekilde, fonksiyon çağrısına fulaşılamaz.

Bu çok açıklayıcı. Bunu yalnızca switchifade kapsamı içinde kullanılabilen yerel olarak kapsamlandırılmış bir değişkeni tanımlamak için kullanabilirsiniz .


9

Bununla birlikte bir "döngü ve bir buçuk" uygulamak mümkündür, ancak bunu yapmanın en iyi yolu olmayabilir:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));

@RichardII Bu bir kelime oyunu mu yoksa ne? Lütfen açıkla.
Dancia

1
@Dancia O, bu oldukça net bir şekilde olduğunu söylüyor değil "olmayabilir" Böyle bir şey yapmanın en iyi yolu, ve bir understatement bir şeydir.
Monica'nın Davası
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.