Considere a seguinte enumeração e instrução 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
}
};
Sou um programador de Objective-C, mas escrevi isso em C puro para um público mais amplo.
Clang / LLVM 4.1 com -Weverything me avisa na linha padrão:
Etiqueta padrão na opção que cobre todos os valores de enumeração
Agora, eu posso ver por que isso existe: em um mundo perfeito, os únicos valores inseridos no argumento theMask
seriam enum, portanto, nenhum padrão é necessário. Mas e se algum hack surgir e lançar um int não inicializado em minha bela função? Minha função será fornecida como uma gota na biblioteca e não tenho controle sobre o que poderia acontecer lá. Usar default
é uma maneira muito elegante de lidar com isso.
Por que os deuses LLVM consideram esse comportamento indigno de seu dispositivo infernal? Devo estar precedendo isso por uma instrução if para verificar o argumento?
fonte
"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
pode ser útil, mas tenha cuidado ao alterar muito seu código para lidar com ele. Alguns desses avisos não são apenas inúteis, mas contraproducentes e são melhor desativados. (Na verdade, esse é o caso de uso para-Weverything
: começar com ele, e desligar o que não faz sentido.)Respostas:
Aqui está uma versão que não sofre os relatórios do clang do problema ou a que você está protegendo:
Killian já explicou por que o clang emite o aviso: se você estender o enum, você entraria no caso padrão que provavelmente não é o que você deseja. O correto é remover o caso padrão e receber avisos de condições não tratadas .
Agora você está preocupado que alguém possa chamar sua função com um valor que esteja fora da enumeração. Parece que falhou em atender ao pré-requisito da função: está documentado esperar um valor da
testingMask
enumeração, mas o programador passou outra coisa. Portanto, cometa um erro de programador usandoassert()
(ouNSCAssert()
como você disse que está usando Objective-C). Faça seu programa falhar com uma mensagem explicando que o programador está fazendo errado, se o programa estiver errado.fonte
return;
e adicionar umassert(false);
ao final (em vez de me repetir listando as enumerações legais em uma inicialassert()
e naswitch
).Ter um
default
rótulo aqui é um indicador de que você está confuso sobre o que está esperando. Como você esgotou todos osenum
valores possíveis explicitamente, elesdefault
não podem ser executados e você também não precisa se proteger contra alterações futuras, porque se você estendeu o valorenum
, a construção já geraria um aviso.Portanto, o compilador percebe que você cobriu todas as bases, mas parece estar pensando que não, e isso sempre é um mau sinal. Ao fazer o mínimo esforço possível para alterar a
switch
forma esperada, você demonstra ao compilador que o que parece estar fazendo é o que realmente está fazendo, e você sabe disso.fonte
Clang está confuso, ter uma declaração padrão de que existe uma prática perfeitamente correta, é conhecida como programação defensiva e é considerada uma boa prática de programação (1). É bastante usado em sistemas de missão crítica, embora talvez não em programação de desktop.
O objetivo da programação defensiva é detectar erros inesperados que, em teoria, nunca aconteceriam. Um erro tão inesperado não é necessariamente o programador que fornece à função uma entrada incorreta ou mesmo um "mal hack". Provavelmente, isso pode ser causado por uma variável corrompida: estouros de buffer, estouro de pilha, código descontrolado e erros semelhantes não relacionados à sua função podem estar causando isso. E, no caso de sistemas embarcados, as variáveis podem mudar devido à EMI, principalmente se você estiver usando circuitos de RAM externos.
Quanto ao que escrever dentro da declaração padrão ... se você suspeitar que o programa deu errado depois que você chegou lá, precisará de algum tipo de tratamento de erros. Em muitos casos, você provavelmente pode simplesmente adicionar uma declaração vazia com um comentário: "inesperado, mas não importa" etc, para mostrar que você pensou na situação improvável.
(1) MISRA-C: 2004 15.3.
fonte
-Weverything
ativa todos os avisos, não uma seleção apropriada para um caso de uso específico. Não se adequar ao seu gosto não é a mesma coisa que confusão.Melhor ainda:
Isso é menos propenso a erros ao adicionar itens à enumeração. Você pode pular o teste para> = 0 se você deixar seus valores de enum sem sinal. Esse método funciona apenas se você não tiver lacunas nos valores da enumeração, mas esse geralmente é o caso.
fonte
Então você obtém um comportamento indefinido e sua
default
vontade não faz sentido. Não há nada que você possa fazer para melhorar isso.Deixe-me ser mais claro. No instante em que alguém passa um não inicializado
int
para sua função, é Comportamento indefinido. Sua função poderia resolver o problema da parada e isso não importaria. É UB. Não há nada que você possa fazer depois que o UB for chamado.fonte
A declaração padrão não ajudaria necessariamente. Se a opção ultrapassar uma enumeração, qualquer valor que não esteja definido na enumeração acabará executando um comportamento indefinido.
Pelo que você sabe, o compilador pode compilar essa opção (com o padrão) como:
Depois que você aciona um comportamento indefinido, não há como voltar atrás.
fonte
enum
tipo é igual ao intervalo de valores de seu tipo inteiro subjacente (que é definido pela implementação e, portanto, deve ser autoconsistente).Eu também prefiro ter um
default:
em todos os casos. Estou atrasado para a festa, como sempre, mas ... alguns outros pensamentos que não vi acima:-Werror
) é proveniente-Wcovered-switch-default
(de-Weverything
mas não-Wall
). Se a sua flexibilidade moral permite que você gire off certas advertências (ie, extirpar algumas coisas-Wall
ou-Weverything
), considere jogando-Wno-covered-switch-default
(ou-Wno-error=covered-switch-default
pelo uso-Werror
), e em geral-Wno-...
para os outros avisos que você encontrar desagradável.gcc
(e comportamento mais genérica emclang
), consultegcc
página de manual para-Wswitch
,-Wswitch-enum
,-Wswitch-default
para o comportamento (diferente) em situações semelhantes de tipos enumerados em declarações switch.Eu também não gosto desse aviso em conceito e não gosto da redação; para mim, as palavras do aviso ("etiqueta padrão ... cobre todos ... valores") sugerem que o
default:
caso será sempre executado, comoNa primeira leitura, era para isso que eu pensava que você estava se deparando - que seu
default:
caso seria sempre executado porque não existebreak;
ou não éreturn;
semelhante. Esse conceito é semelhante (ao meu ouvido) a outros comentários no estilo de babá (embora ocasionalmente úteis) que surjamclang
. Sefoo == 1
, ambas as funções serão executadas; seu código acima tem esse comportamento. Ou seja, falhe apenas se você quiser continuar executando o código de casos subseqüentes! Parece, no entanto, não ser o seu problema.Correndo o risco de ser pedante, alguns outros pensamentos para a plenitude:
int
ou algo para esta função, que é explícita a intenção de consumir o seu próprio tipo específico, o compilador deve protegê-lo igualmente bem nessa situação com um aviso agressivo ou de erro. MAS isso não acontece! (Ou seja, parece que pelo menosgcc
eclang
não fazenum
verificação de tipo, mas ouvi dizer queicc
sim ). Como você não está obtendo o tipo -segurança, você pode obter o valor -segurança conforme discutido acima. Caso contrário, conforme sugerido no TFA, considere umstruct
ou algo que possa fornecer segurança de tipo.enum
, comoMaskValueIllegal
não oferecer suporte a elecase
no seuswitch
. Isso seria comido pelodefault:
(além de qualquer outro valor maluco)Viva a codificação defensiva!
fonte
Aqui está uma sugestão alternativa:
O OP está tentando se proteger contra o caso em que alguém passa por
int
onde é esperado um enum . Ou, mais provavelmente, quando alguém vinculou uma biblioteca antiga a um programa mais recente usando um cabeçalho mais novo com mais casos.Por que não mudar o interruptor para lidar com o
int
caso? A adição de uma conversão antes do valor no comutador elimina o aviso e até fornece uma certa dica sobre por que o padrão existe.Acho isso muito menos censurável do que
assert()
testar cada um dos valores possíveis ou até assumir que o intervalo de valores enum está bem ordenado, para que um teste mais simples funcione. Essa é apenas uma maneira feia de fazer o que o padrão faz com precisão e beleza.fonte