¿Sintaxis válida pero inútil en switch-case?

207

A través de un pequeño error tipográfico, encontré accidentalmente esta construcción:

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;
}

Parece que la printfparte superior de la switchdeclaración es válida, pero también completamente inalcanzable.

Obtuve una compilación limpia, sin siquiera una advertencia sobre el código inalcanzable, pero esto parece inútil.

¿Debe un compilador marcar esto como código inalcanzable?
¿Tiene esto algún propósito?

abelenky
fuente
51
GCC tiene una bandera especial para esto. Es-Wswitch-unreachable
Eli Sadoff
57
"¿Sirve esto para algo?" Bueno, puedes gotoentrar y salir de la parte inalcanzable, que puede ser útil para varios hacks.
HolyBlackCat
12
@HolyBlackCat ¿No sería eso para todos los códigos inalcanzables?
Eli Sadoff
28
@EliSadoff De hecho. Supongo que no tiene ningún propósito especial . Apuesto a que está permitido solo porque no hay razón para prohibirlo. Después de todo, switches solo un condicional gotocon múltiples etiquetas. Hay más o menos las mismas restricciones en su cuerpo que las que tendría en un bloque de código regular lleno de etiquetas de goto.
HolyBlackCat
16
Vale la pena señalar que el ejemplo de @MooingDuck es una variante en el dispositivo de Duff ( en.wikipedia.org/wiki/Duff's_device )
Michael Anderson

Respuestas:

226

Quizás no sea el más útil, pero no del todo inútil. Puede usarlo para declarar una variable local disponible dentro del switchalcance.

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

El estándar ( N1579 6.8.4.2/7) tiene la siguiente muestra:

EJEMPLO En el fragmento de programa artificial

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

el objeto cuyo identificador iexiste con una duración de almacenamiento automática (dentro del bloque) pero nunca se inicializa y, por lo tanto, si la expresión de control tiene un valor distinto de cero, la llamada a la printffunción accederá a un valor indeterminado. Del mismo modo, fno se puede acceder a la llamada a la función .

PS BTW, la muestra no es un código C ++ válido. En ese caso ( N4140 6.7/3énfasis mío):

Un programa que salta 90 desde un punto donde una variable con duración de almacenamiento automático no está dentro del alcance hasta un punto donde está dentro del alcance está mal formado a menos que la variable tenga tipo escalar , tipo de clase con un constructor trivial predeterminado y un destructor trivial, una versión calificada para cv de uno de estos tipos, o una matriz de uno de los tipos anteriores y se declara sin un inicializador (8.5).


90) La transferencia de la condición de una switchdeclaración a la etiqueta de un caso se considera un salto a este respecto.

Por lo que reemplazar int i = 4;con int i;lo convierte en un C ++ válida.

AlexD
fuente
3
"... pero nunca se inicializa ..." Parece que ise inicializó a 4, ¿qué me estoy perdiendo?
yano
77
Tenga en cuenta que si la variable es static, se inicializará a cero, por lo que también hay un uso seguro para esto.
Leushenko
23
@yano Siempre saltamos sobre la i = 4;inicialización, por lo que nunca tiene lugar.
AlexD
12
Ja por supuesto! ... punto de la cuestión ... caramba. El deseo es fuerte para eliminar esta estupidez
yano
1
¡Agradable! A veces necesitaba una variable temporal dentro de a case, y siempre tenía que usar diferentes nombres en cada una caseo definirla fuera del interruptor.
SJuan76
98

¿Tiene esto algún propósito?

Si. Si en lugar de una declaración, coloca una declaración antes de la primera etiqueta, esto puede tener mucho sentido:

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

Las reglas para declaraciones y declaraciones se comparten para bloques en general, por lo que es la misma regla que permite que también permita declaraciones allí.


También vale la pena mencionar que si la primera instrucción es una construcción de bucle, pueden aparecer etiquetas de caso en el cuerpo del bucle:

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

No escriba un código como este si hay una forma más legible de escribirlo, pero es perfectamente válido y f()se puede acceder a la llamada.


fuente
@MatthieuM Duff's Device tiene etiquetas de caso dentro de un bucle, pero comienza con una etiqueta de caso antes del bucle.
2
No estoy seguro de si debería votar por el ejemplo interesante o votar por la locura de escribir esto en un programa real :). Felicidades por sumergirse en el abismo y regresar de una pieza.
Liviu T.
@ChemicalEngineer: si el código es parte de un bucle, como está en el dispositivo de Duff, { /*code*/ switch(x) { } }puede parecer más limpio, pero también está mal .
Ben Voigt
39

Hay un uso famoso de este llamado Dispositivo de Duff .

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);
}

Aquí copiamos un búfer señalado por froma un búfer apuntado por to. Copiamos countinstancias de datos.

La do{}while()declaración comienza antes de la primera caseetiqueta, y las caseetiquetas están incrustadas dentro de do{}while().

Esto reduce el número de ramas condicionales al final del do{}while()ciclo encontradas por aproximadamente un factor de 4 (en este ejemplo; la constante se puede ajustar al valor que desee).

Ahora, los optimizadores a veces pueden hacer esto por usted (especialmente si están optimizando las instrucciones de transmisión / vectorizadas), pero sin la optimización guiada por perfil no pueden saber si espera que el bucle sea grande o no.

En general, las declaraciones de variables pueden ocurrir allí y usarse en todos los casos, pero estar fuera del alcance después de que finalice el cambio. (tenga en cuenta que se omitirá cualquier inicialización)

Además, el flujo de control que no es específico del interruptor puede llevarlo a esa sección del bloque de interruptores, como se ilustra arriba, o con un goto.

Yakk - Adam Nevraumont
fuente
2
Por supuesto, esto aún sería posible sin permitir declaraciones por encima del primer caso, ya que el orden de do {y case 0:no importa, ambos sirven para colocar un objetivo de salto en el primero *to = *from++;.
Ben Voigt
1
@BenVoigt Yo diría que poner el do {es más legible. Sí, discutir sobre la legibilidad para el dispositivo de Duff es estúpido e inútil y probablemente sea una forma simple de volverse loco.
Financia la demanda de Mónica el
1
@QPaysTaxes Usted debe comprobar fuera de Simon Tatham corrutinas en C . O tal vez no.
Jonas Schäfer el
@ JonasSchäfer Divertidamente, eso es básicamente lo que las corutinas C ++ 20 van a hacer por usted.
Yakk - Adam Nevraumont
15

Suponiendo que está utilizando gcc en Linux, le habría dado una advertencia si está utilizando 4.4 o una versión anterior.

La opción -Wunreachable-code se eliminó en gcc 4.4 en adelante.

16 toneladas
fuente
¡Haber experimentado el problema de primera mano siempre ayuda!
16tons
@JonathanLeffler: El problema general de que las advertencias de gcc sean susceptibles al conjunto particular de pases de optimización seleccionados sigue siendo cierto, desafortunadamente, y hace que la experiencia del usuario sea deficiente. Es realmente molesto tener una compilación de depuración limpia seguida de una compilación de lanzamiento fallida: /
Matthieu M.
@MatthieuM .: Parece que tal advertencia sería extremadamente fácil de detectar en casos que involucren análisis gramaticales en lugar de semánticos [por ejemplo, el código sigue un "si" que tiene retornos en ambas ramas], y hace que sea más fácil sofocar la "molestia" advertencias Por otro lado, hay algunos casos en los que es útil tener errores o advertencias en las versiones de lanzamiento que no están en las versiones de depuración (por lo menos, en lugares donde se supone que se debe limpiar un truco que se instala para la depuración) lanzamiento).
supercat
1
@MatthieuM .: Si se requiriera un análisis semántico significativo para descubrir que una determinada variable siempre será falsa en algún punto del código, el código que está condicionado a que esa variable sea verdadera sería inalcanzable solo si se realizara dicho análisis. Por otro lado, consideraría un aviso de que dicho código era inalcanzable de manera bastante diferente a una advertencia sobre el código inalcanzable sintácticamente, ya que debería ser completamente normal tener varias condiciones posibles con algunas configuraciones de proyecto pero no con otras. A veces puede ser útil para los programadores ...
supercat
1
... saber por qué algunas configuraciones generan código más grande que otras [por ejemplo, porque un compilador podría considerar alguna condición como imposible en una configuración pero no en otra], pero eso no significa que haya algo "incorrecto" en el código que podría optimizarse en esa moda con algunas configuraciones.
supercat
11

No solo para declaración variable sino también para salto avanzado. Puede utilizarlo bien si y solo si no es propenso al código de espagueti.

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;
}

Huellas dactilares

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

Cabe señalar que switch-case es una de las cláusulas de control de flujo más rápido. Por lo tanto, debe ser muy flexible para el programador, lo que a veces implica casos como este.

Sanchke Dellowar
fuente
¿Y cuál es la diferencia entre nocase:y default:?
i486
@ i486 Cuando i=4no se dispara nocase.
Sanchke Dellowar
@SanchkeDellowar a eso me refiero.
njzk2
¿Por qué diablos se haría eso en lugar de simplemente poner el caso 1 antes del caso 0 y usar fallthrough?
Jonas Schäfer el
@JonasWielicki En este objetivo, podrías hacer eso. Pero este código es solo una muestra de lo que se puede hacer.
Sanchke Dellowar
11

Cabe señalar que prácticamente no hay restricciones estructurales en el código dentro de la switchdeclaración o en el lugar donde case *:se colocan las etiquetas dentro de este código *. Esto hace posible trucos de programación como el dispositivo de duff , una implementación posible que se ve así:

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: ;
    }
}

Verá, el código entre el switch(n%8) {y la case 7:etiqueta es definitivamente accesible ...


* Como supercat señaló afortunadamente en un comentario : desde C99, gotoni una etiqueta ni una etiqueta (ya sea una case *:etiqueta o no) pueden aparecer dentro del alcance de una declaración que contiene una declaración VLA. Por lo tanto, no es correcto decir que no hay restricciones estructurales en la colocación de las case *:etiquetas. Sin embargo, el dispositivo de Duff es anterior al estándar C99, y de todos modos no depende del VLA. Sin embargo, me sentí obligado a insertar un "virtualmente" en mi primera oración debido a esto.

cmaster - restablecer monica
fuente
La adición de matrices de longitud variable condujo a la imposición de restricciones estructurales relacionadas con ellas.
supercat
@supercat ¿Qué tipo de restricciones?
cmaster - reinstalar monica
1
Ni una etiqueta gotoni switch/case/defaultpuede aparecer dentro del alcance de cualquier objeto o tipo declarado de forma variable. Esto significa efectivamente que si un bloque contiene cualquier declaración de objetos o tipos de matriz de longitud variable, cualquier etiqueta debe preceder a esas declaraciones. Hay un poco de palabrería confusa en el Estándar que sugiere que en algunos casos el alcance de una declaración VLA se extiende a la totalidad de una declaración de cambio; ver stackoverflow.com/questions/41752072/… para mi pregunta sobre eso.
supercat
@supercat: Simplemente entendiste mal esa palabrería (lo que supongo es por qué eliminaste tu pregunta). Impone un requisito sobre el alcance en el que se puede definir un VLA. No extiende ese alcance, solo invalida ciertas definiciones de VLA.
Keith Thompson
@KeithThompson: Sí, lo había entendido mal. El uso extraño del tiempo presente en la nota al pie hizo que las cosas fueran confusas, y creo que el concepto podría haberse expresado mejor como una prohibición: "Una declaración de cambio cuyo cuerpo contenga una declaración de VLA no incluirá ningún cambio o etiqueta de caso dentro del alcance de esa declaración VLA ".
supercat
10

Obtuviste tu respuesta relacionada con la opción requeridagcc -Wswitch-unreachable para generar la advertencia, esta respuesta es elaborar la parte de usabilidad / dignidad .

Citando directamente del C11capítulo §6.8.4.2, ( énfasis mío )

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

el objeto cuyo identificador iexiste con una duración de almacenamiento automática (dentro del bloque) pero nunca se inicializa y, por lo tanto, si la expresión de control tiene un valor distinto de cero, la llamada a la printf función accederá a un valor indeterminado. Del mismo modo, fno se puede acceder a la llamada a la función .

Lo cual se explica por sí mismo. Puede usar esto para definir una variable de ámbito local que esté disponible solo dentro del switchalcance de la instrucción.

Sourav Ghosh
fuente
9

Es posible implementar un "ciclo y medio" con él, aunque podría no ser la mejor manera de hacerlo:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));
celtschk
fuente
@RichardII ¿Es un juego de palabras o qué? Por favor explique.
Dancia
1
@Dancia Él dice que esta claramente no es la mejor manera de hacer algo así, y que "tal vez no" es una subestimación.
Financia la demanda de Mónica el