Considere la siguiente instrucción enum y switch:
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
}
};
Soy un programador de Objective-C, pero he escrito esto en C puro para un público más amplio.
Clang / LLVM 4.1 con -Weverything me advierte en la línea predeterminada:
Etiqueta predeterminada en el interruptor que cubre todos los valores de enumeración
Ahora, puedo ver por qué esto está ahí: en un mundo perfecto, los únicos valores que entran en el argumento theMask
estarían en la enumeración, por lo que no es necesario ningún valor predeterminado. Pero, ¿qué pasa si aparece algún truco y arroja un int no inicializado a mi hermosa función? Mi función se proporcionará como una caída en la biblioteca, y no tengo control sobre lo que podría entrar allí. Usar default
es una forma muy ordenada de manejar esto.
¿Por qué los dioses LLVM consideran este comportamiento indigno de su dispositivo infernal? ¿Debería estar precediendo esto por una declaración if para verificar el argumento?
fuente
"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
puede ser útil, pero tenga cuidado al mutar demasiado su código para lidiar con él. Algunas de esas advertencias no solo son inútiles sino contraproducentes, y es mejor desactivarlas. (De hecho, ese es el caso de uso-Weverything
: comience con él y apague lo que no tiene sentido.)Respuestas:
Aquí hay una versión que no sufre ni del informe del problema ni del que estás protegiendo:
Killian ya ha explicado por qué el sonido metálico emite la advertencia: si extendió la enumeración, caería en el caso predeterminado que probablemente no sea lo que desea. Lo correcto es eliminar el caso predeterminado y obtener advertencias para condiciones no controladas .
Ahora le preocupa que alguien pueda llamar a su función con un valor que esté fuera de la enumeración. Eso suena como no cumplir con el requisito previo de la función: está documentado esperar un valor de la
testingMask
enumeración, pero el programador ha pasado algo más. Entonces haga que sea un error de programador usandoassert()
(oNSCAssert()
como dijo que está usando Objective-C). Haga que su programa se bloquee con un mensaje que explica que el programador lo está haciendo mal, si el programador lo hace mal.fuente
return;
y agregar unassert(false);
al final (en lugar de repetirme enumerando las enumeraciones legales en una inicialassert()
y en elswitch
).Tener una
default
etiqueta aquí es un indicador de que estás confundido acerca de lo que estás esperando. Dado que ha agotado todos losenum
valores posibles explícitamente,default
posiblemente no se pueda ejecutar, y tampoco lo necesita para protegerse contra cambios futuros, porque si extendió elenum
, la construcción ya generaría una advertencia.Entonces, el compilador nota que ha cubierto todas las bases pero parece estar pensando que no lo ha hecho, y eso siempre es una mala señal. Al hacer un esfuerzo mínimo para cambiar la
switch
forma esperada, demuestra al compilador que lo que parece estar haciendo es lo que realmente está haciendo, y lo sabe.fuente
Clang está confundido, teniendo una declaración predeterminada, hay una práctica perfectamente buena, se conoce como programación defensiva y se considera una buena práctica de programación (1). Se usa mucho en sistemas de misión crítica, aunque quizás no en la programación de escritorio.
El propósito de la programación defensiva es detectar errores inesperados que en teoría nunca sucederían. Tal error inesperado no es necesariamente que el programador le dé a la función una entrada incorrecta, o incluso un "hack malvado". Lo más probable es que pueda deberse a una variable corrupta: desbordamientos de búfer, desbordamiento de pila, código desbocado y errores similares no relacionados con su función podrían estar causando esto. Y en el caso de los sistemas embebidos, las variables podrían cambiar debido a EMI, particularmente si está utilizando circuitos RAM externos.
En cuanto a qué escribir dentro de la declaración predeterminada ... si sospecha que el programa se volvió loco una vez que terminó allí, entonces necesita algún tipo de manejo de errores. En muchos casos, es probable que simplemente agregue una declaración vacía con un comentario: "inesperado pero no importa", etc., para mostrar que ha pensado en la situación poco probable.
(1) MISRA-C: 2004 15.3.
fuente
-Weverything
activa todas las advertencias, no una selección apropiada para un caso de uso en particular. No ajustarse a su gusto no es lo mismo que la confusión.Mejor aún:
Esto es menos propenso a errores al agregar elementos a la enumeración. Puede omitir la prueba para> = 0 si hace que sus valores de enumeración no estén firmados. Este método solo funciona si no tiene huecos en los valores de su enumeración, pero ese suele ser el caso.
fuente
Entonces obtienes Comportamiento Indefinido , y tu
default
voluntad no tendrá sentido. No hay nada que puedas hacer para mejorar esto.Déjame ser más claro. En el momento en que alguien pasa un no inicializado
int
a su función, es Comportamiento indefinido. Su función podría resolver el problema de detención y no importaría. Es UB. No hay nada que puedas hacer una vez que UB ha sido invocado.fuente
La declaración predeterminada no necesariamente ayudaría. Si el cambio está sobre una enumeración, cualquier valor que no esté definido en la enumeración terminará ejecutando un comportamiento indefinido.
Por lo que sabes, el compilador puede compilar ese interruptor (con el valor predeterminado) como:
Una vez que desencadena un comportamiento indefinido, no hay vuelta atrás.
fuente
enum
tipo es el mismo que el rango de valores de su tipo entero subyacente (que está definido por la implementación, por lo tanto, debe ser autoconsistente).También prefiero tener un
default:
en todos los casos. Llego tarde a la fiesta como de costumbre, pero ... algunos otros pensamientos que no vi arriba:-Werror
) proviene de-Wcovered-switch-default
(de-Weverything
pero no-Wall
). Si su flexibilidad moral le permite desactivar ciertas advertencias (es decir, eliminar algunas cosas de-Wall
o-Weverything
), considere tirar-Wno-covered-switch-default
(o-Wno-error=covered-switch-default
al usar-Werror
) y, en general,-Wno-...
otras advertencias que considere desagradables.gcc
(y un comportamiento más genéricoclang
), consultegcc
la página de manual de-Wswitch
,-Wswitch-enum
,-Wswitch-default
de (diferentes) el comportamiento en situaciones similares de los tipos enumerados en los estados de conmutación.Tampoco me gusta esta advertencia en concepto, y no me gusta su redacción; para mí, las palabras de la advertencia ("etiqueta predeterminada ... cubre todos los ... valores") sugieren que el
default:
caso siempre se ejecutará, comoEn una primera lectura, esto es lo que pensé que se estaba ejecutando en - que su
default:
caso siempre sería ejecutado porque no hay esbreak;
oreturn;
o similar. Este concepto es similar (a mi oído) a otros comentarios de estilo de niñera (aunque ocasionalmente útiles) que se desprendenclang
. Sifoo == 1
, ambas funciones se ejecutarán; Su código anterior tiene este comportamiento. Es decir, ¡no se separe solo si desea seguir ejecutando código de casos posteriores! Esto parece, sin embargo, no ser su problema.A riesgo de ser pedante, algunos otros pensamientos para completar:
int
o algo para esta función, que es la intención explícita de consumir su propio tipo específico, el compilador debe protegerte igualmente bien en esa situación con una advertencia o error agresiva. PERO no lo hace! (Es decir, parece que al menosgcc
yclang
noenum
verificas el tipo, pero escuché queicc
sí ). Como no obtiene el tipo -safety, puede obtener el valor -safety como se discutió anteriormente. De lo contrario, como se sugiere en TFA, considere unostruct
o algo que pueda proporcionar seguridad de tipo.enum
comoMaskValueIllegal
, y no admitir esocase
en suswitch
. Eso sería comido por eldefault:
(además de cualquier otro valor loco)¡Viva la codificación defensiva!
fuente
Aquí hay una sugerencia alternativa:
el OP está tratando de protegerse contra el caso en el que alguien pasa y se espera
int
una enumeración . O, más probablemente, cuando alguien ha vinculado una biblioteca antigua con un programa más nuevo utilizando un encabezado más nuevo con más casos.¿Por qué no cambiar el interruptor para manejar el
int
caso? Agregar un reparto delante del valor en el interruptor elimina la advertencia e incluso proporciona un cierto indicio sobre por qué existe el valor predeterminado.Me parece mucho menos objetable que
assert()
probar cada uno de los valores posibles o incluso suponer que el rango de valores de enumeración está bien ordenado para que funcione una prueba más simple. Esta es solo una forma fea de hacer lo que el predeterminado hace con precisión y belleza.fuente