Declaración de cambio: ¿el valor predeterminado debe ser el último caso?

12 minutos de lectura

avatar de usuario
tanascio

Considera lo siguiente switch declaración:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

Este código compila, pero ¿es válido (= comportamiento definido) para C90/C99? Nunca he visto un código donde el por defecto caso no es el último caso.

EDITAR:

Como Jon Cage y KillianDS escribir: este es un código realmente feo y confuso y soy muy consciente de ello. Solo estoy interesado en la sintaxis general (¿está definida?) Y el resultado esperado.

  • +1 Ni siquiera consideré ese comportamiento.

    – Jaime Wong

    24 de junio de 2010 a las 12:59

  • @Péter Török no, el orden no importa: si el valor coincide con la etiqueta constante en cualquier caso, el control saltará a esa declaración que sigue a la etiqueta; de lo contrario, el control saltará a la declaración que sigue a la etiqueta predeterminada, si está presente.

    –Pete Kirkham

    24 de junio de 2010 a las 13:10

  • @Jon Cage goto no es malo ¡Los seguidores del culto de carga son! No te imaginas a qué extremos puede llegar la gente para evitar goto porque supuestamente es tan malvado, haciendo un verdadero lío ilegible de su código.

    – Patrick Schlüter

    24 de junio de 2010 a las 13:34

  • Quizás evil fue una mala elección de palabras, tal vez easily abused o easily misunderstood. No hay ninguna razón por la que evitar las necesidades de goto conduzca a un código menos legible y, en general, si necesita goto, sugeriría que sea una señal de que el código del código esté mejor estructurado en primer lugar. Si tiene algún ejemplo concreto que refute mi punto, me interesaría mucho verlo. No estoy atacando aquí, solo tuve que corregir algunos errores desagradables causados ​​​​por goto y construcciones de lenguaje similares en el pasado 🙂

    – Jon Cage

    24 de junio de 2010 a las 13:57

  • yo suelo goto principalmente para simular algo como un finally cláusula en funciones, donde los recursos (archivos, memoria) deben liberarse al detenerse, y repetir para cada caso de error una lista de free y close no ayuda a la legibilidad. Aunque hay un uso de goto que me gustaría evitar pero no puedo, es cuando quiero salir de un bucle y estoy dentro de un switch en ese bucle.

    – Patrick Schlüter

    24 de junio de 2010 a las 14:18

avatar de usuario
Salil

Las declaraciones de caso y la declaración predeterminada pueden aparecer en cualquier orden en la declaración de cambio. La cláusula predeterminada es una cláusula opcional que coincide si ninguna de las constantes en las declaraciones de caso puede coincidir.

Buen ejemplo :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

muy útil si desea que sus casos se presenten en un orden lógico en el código (como en caso de no decir caso 1, caso 3, caso 2/predeterminado) y sus casos son muy largos, por lo que no desea repetir el caso completo código en la parte inferior para el valor predeterminado

  • Este es exactamente el escenario en el que generalmente coloco el valor predeterminado en algún lugar que no sea el final… hay un orden lógico para los casos explícitos (1, 2, 3) y quiero que el valor predeterminado se comporte exactamente igual que uno de los casos explícitos que no es el último.

    – El arte de la guerra

    18 de diciembre de 2012 a las 16:10

avatar de usuario
Seguro

El estándar C99 no es explícito al respecto, pero tomando todos los hechos en conjunto, es perfectamente válido.

A case y default etiqueta son equivalentes a una goto etiqueta. Consulte 6.8.1 Declaraciones etiquetadas. Especialmente interesante es 6.8.1.4, que habilita el ya mencionado Dispositivo de Duff:

Cualquier declaración puede estar precedida por un prefijo que declara un identificador como un nombre de etiqueta. Las etiquetas en sí mismas no alteran el flujo de control, que continúa sin obstáculos a través de ellas.

Editar: El código dentro de un interruptor no es nada especial; es un bloque normal de código como en un if-instrucción, con etiquetas de salto adicionales. Esto explica el comportamiento fallido y por qué break es necesario.

6.8.4.2.7 incluso da un ejemplo:

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

En el fragmento de programa artificial, el objeto cuyo identificador es i existe con una duración de almacenamiento automático (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 función printf accederá a un valor indeterminado. De manera similar, la llamada a la función f no se puede alcanzar.

Las constantes de caso deben ser únicas dentro de una declaración de cambio:

6.8.4.2.3 La expresión de cada etiqueta de caso debe ser una expresión de constante entera y dos de las expresiones de constante de caso en la misma declaración de cambio no deben tener el mismo valor después de la conversión. Puede haber como máximo una etiqueta predeterminada en una declaración de cambio.

Se evalúan todos los casos, luego salta a la etiqueta predeterminada, si se proporciona:

6.8.4.2.5 Las promociones de enteros se realizan en la expresión de control. La expresión constante en cada etiqueta de caso se convierte al tipo promocionado de la expresión de control. Si un valor convertido coincide con el de la expresión de control promovida, el control salta a la instrucción que sigue a la etiqueta de caso coincidente. De lo contrario, si hay una etiqueta predeterminada, el control salta a la declaración etiquetada. Si no coincide ninguna expresión de constante de mayúsculas y minúsculas convertida y no hay una etiqueta predeterminada, no se ejecuta ninguna parte del cuerpo del conmutador.

  • @HeathHunnicutt Claramente no entendiste el propósito del ejemplo. El código no está compuesto por este póster, sino que se tomó directamente del estándar C, como una ilustración de cuán extrañas son las declaraciones de cambio y cómo las malas prácticas conducirán a errores. Si te hubieras molestado en leer el texto debajo del código, te habrías dado cuenta.

    – Lundin

    13 de noviembre de 2013 a las 14:49


  • +1 para compensar el voto negativo. Votar negativamente a alguien por citar el estándar C parece bastante duro.

    – Lundin

    13 de noviembre de 2013 a las 14:50


  • @Lundin No estoy votando en contra del estándar C, y no pasé por alto nada como sugieres. Rechacé la mala pedagogía de usar un ejemplo malo e innecesario. En particular, ese ejemplo se relaciona con una situación completamente diferente a la que se le preguntó. Podría continuar, pero “gracias por sus comentarios”.

    –Heath Hunnicutt

    13 de noviembre de 2013 a las 15:34

  • Intel le dice que coloque el código más frecuente primero en una declaración de cambio en Reorganización de ramales y bucles para evitar predicciones erróneas. Estoy aquí porque tengo un default caso que domina otros casos en aproximadamente 100: 1, y no sé si es válido o indefinido para hacer default el primer caso

    – jww

    28 de septiembre de 2016 a las 3:46


  • @JaveneCPPMcGowan Estoy seguro de que Intel jww se refiere a la empresa que produce chips de computadora, no a la palabra común inteligencia. Y su pensamiento no es del todo correcto: el compilador se toma la libertad de convertir algunos casos de cambio en jmp incondicional parametrizado (registro indirecto), y convertir otros casos de cambio en ramas condicionales.

    – 把友情留在无盐

    11 de enero de 2020 a las 4:31

avatar de usuario
kriss

Es válido y muy útil en algunos casos.

Considere el siguiente código:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

El punto es que el código anterior es más legible y eficiente que en cascada if. podrías poner default al final, pero no tiene sentido ya que centrará su atención en los casos de error en lugar de los casos normales (que aquí es el default caso).

En realidad, no es un buen ejemplo, en poll sabes cuántos eventos pueden ocurrir como máximo. Mi verdadero punto es que hay están casos con un conjunto definido de valores de entrada donde hay ‘excepciones’ y casos normales. Si es mejor anteponer las excepciones o los casos normales es una cuestión de elección.

En el campo del software pienso en otro caso muy habitual: las recursiones con algunos valores terminales. Si puedes expresarlo usando un interruptor, default será el valor habitual que contiene la llamada recursiva y los elementos distinguidos (casos individuales) de los valores terminales. Por lo general, no hay necesidad de centrarse en los valores terminales.

Otra razón es que el orden de los casos puede cambiar el comportamiento del código compilado, y eso es importante para el rendimiento. La mayoría de los compiladores generarán código ensamblador compilado en el mismo orden en que aparece el código en el conmutador. Eso hace que el primer caso sea muy diferente de los demás: todos los casos, excepto el primero, implicarán un salto y eso vaciará las tuberías del procesador. Puede entenderlo como un predictor de rama predeterminado para ejecutar el primer caso que aparece en el conmutador. Si un caso es mucho más común que los demás, entonces tiene muy buenas razones para ponerlo como el primer caso.

Leer los comentarios es la razón específica por la que el autor original hizo esa pregunta después de leer Reorganización de Branch Loop del compilador Intel sobre optimización de código.

Entonces se convertirá en un arbitraje entre la legibilidad del código y el rendimiento del código. Probablemente sea mejor poner un comentario para explicarle al futuro lector por qué un caso aparece primero.

  • +1 por dar un (buen) ejemplo sin el comportamiento fallido.

    – KillianDS

    24 de junio de 2010 a las 13:30

  • … sin embargo, pensándolo bien, no estoy convencido de que tener el valor predeterminado en la parte superior sea bueno porque muy pocas personas lo buscarían allí. Podría ser mejor asignar el retorno a una variable y manejar el éxito en un lado de un si y los errores en el otro lado con una declaración de caso.

    – Jon Cage

    24 de junio de 2010 a las 13:43

  • @Jon: solo escríbelo. Agrega ruido sintáctico sin ningún beneficio de legibilidad. Y, si el valor predeterminado está en la parte superior, realmente no hay necesidad de mirarlo, es realmente obvio (podría ser más complicado si lo pones en el medio).

    – kriss

    24 de junio de 2010 a las 14:27

  • Por cierto, no me gusta mucho la sintaxis de cambio/caso C. Preferiría con mucho poder poner varias etiquetas después de un caso en lugar de estar obligado a poner varias etiquetas sucesivas case. Lo que es deprimente es que parece azúcar sintáctico y no romperá ningún código existente si es compatible.

    – kriss

    24 de junio de 2010 a las 14:57

  • @kriss: Estuve medio tentado de decir “¡Yo tampoco soy un programador de python!” 🙂

    –Andrew Grimm

    5 de noviembre de 2010 a las 12:05

sí, esto es válido, y bajo algunas circunstancias es incluso útil. En general, si no lo necesita, no lo haga.

avatar de usuario
patricio schlüter

No hay un orden definido en una declaración de cambio. Puede ver los casos como algo así como una etiqueta con nombre, como un goto etiqueta. Al contrario de lo que la gente parece pensar aquí, en el caso del valor 2 no se salta a la etiqueta predeterminada. Para ilustrar con un ejemplo clásico, aquí está dispositivo de Duffque es el niño del cartel de los extremos de switch/case C ª.

send(to, from, count)
register short *to, *from;
register count;
{
  register 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);
  }
}

  • Y para cualquiera que no esté familiarizado con el dispositivo de Duff, este código es completamente ilegible…

    – KillianDS

    24 de junio de 2010 a las 13:34


  • El dispositivo de Duff es un ejemplo perfecto de “Solo porque puedas, no significa que debas”

    – Andrés

    10 oct 2021 a las 11:55

avatar de usuario
Matias Kinnunen

Un escenario en el que consideraría apropiado tener la default El caso ubicado en otro lugar que no sea al final de una declaración de cambio está en una máquina de estado donde un estado no válido debe restablecer la máquina y proceder como si fuera el estado inicial. Por ejemplo:

switch(widget_state)
{
  default:  /* Fell off the rails--reset and continue */
    widget_state = WIDGET_START;
    /* Fall through */
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
}

Un arreglo alternativo, si un estado inválido no debe reiniciar la máquina pero debe ser fácilmente identificable como un estado inválido:

switch(widget_state)
{
  case WIDGET_IDLE:
    widget_ready = 0;
    widget_hardware_off();
    break;
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
  default:
    widget_state = WIDGET_INVALID_STATE;
    /* Fall through */
  case WIDGET_INVALID_STATE:
    widget_ready = 0;
    widget_hardware_off();
    ... do whatever else is necessary to establish a "safe" condition
}

El código en otro lugar puede verificar si hay widget_state == WIDGET_INVALID_STATE y proporcionar cualquier informe de error o comportamiento de restablecimiento de estado que parezca apropiado. Por ejemplo, el código de barra de estado podría mostrar un ícono de error, y la opción de menú “iniciar widget” que está deshabilitada en la mayoría de los estados que no están inactivos podría habilitarse para WIDGET_INVALID_STATE así como también WIDGET_IDLE.

  • Y para cualquiera que no esté familiarizado con el dispositivo de Duff, este código es completamente ilegible…

    – KillianDS

    24 de junio de 2010 a las 13:34


  • El dispositivo de Duff es un ejemplo perfecto de “Solo porque puedas, no significa que debas”

    – Andrés

    10 oct 2021 a las 11:55

avatar de usuario
brennan vicente

Interviniendo con otro ejemplo: esto puede ser útil si “predeterminado” es un caso inesperado y desea registrar el error pero también hacer algo sensato. Ejemplo de parte de mi propio código:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad