Perché Clang / LLVM mi avvisa dell'utilizzo di default in un'istruzione switch in cui sono trattati tutti i casi enumerati?

30

Considera la seguente enumerazione e la seguente dichiarazione:

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

Sono un programmatore Objective-C, ma l'ho scritto in puro C per un pubblico più ampio.

Clang / LLVM 4.1 with -Wissything mi avverte sulla linea predefinita:

Default label in switch which covers all enumeration values

Ora, posso capire perché questo è lì: in un mondo perfetto, gli unici valori che entrano nell'argomento theMask sarebbero nell'enumerazione, quindi non è necessario alcun valore predefinito. Ma cosa succede se qualche hack arriva e getta un int non inizializzato nella mia bella funzione? La mia funzione sarà fornita come una goccia in biblioteca, e non ho il controllo su cosa potrebbe andare lì dentro. L'utilizzo di default è un modo molto accurato per gestirlo.

Perché gli dei LLVM ritengono questo comportamento indegno del loro dispositivo infernale? Dovrei precederlo con un'istruzione if per verificare l'argomento?

    
posta Swizzlr 13.12.2012 - 13:33
fonte

8 risposte

25

Ecco una versione che non presenta né il problema dei rapporti di clang né quello di cui ti stai difendendo:

void myFunction(testingMask theMask) {
    assert(theMask == MaskValueUno || theMask == MaskValueDos);
    switch (theMask) {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
}

Killian ha già spiegato perché il clang emette l'avvertimento: se hai esteso l'enum, cadi nel caso predefinito che probabilmente non è quello che vuoi. La cosa corretta da fare è rimuovere il caso predefinito e ricevere avvisi per le condizioni non gestite .

Ora sei preoccupato che qualcuno possa chiamare la tua funzione con un valore che non rientra nell'enumerazione. Sembra che non riescano a soddisfare il prerequisito della funzione: è documentato aspettarsi un valore dall'enumerazione testingMask ma il programmatore ha passato qualcos'altro. Quindi fai in modo che un errore del programmatore utilizzi assert() (o NSCAssert() come hai detto che stai usando Objective-C). Fai in modo che il tuo programma si arresti in modo anomalo con un messaggio che spiega che il programmatore sta sbagliando, se il programmatore non funziona correttamente.

    
risposta data 13.12.2012 - 13:57
fonte
8

Avere un'etichetta default qui è un indicatore del fatto che sei confuso su ciò che ti aspetti. Poiché hai esaurito tutti i possibili valori di enum esplicitamente, non è possibile eseguire default e non è necessario proteggerti dalle modifiche future, perché se estendi enum , il costrutto sarebbe già genera un avviso.

Quindi, il compilatore nota che hai coperto tutte le basi, ma sembra che pensi che tu non abbia, e questo è sempre un brutto segno. Facendo il minimo sforzo per cambiare il switch nella forma prevista, si dimostra al compilatore che ciò che si sta facendo è ciò che si sta effettivamente facendo, e lo si conosce.

    
risposta data 13.12.2012 - 13:42
fonte
5

Clang è confuso, con un'istruzione di default c'è una buona pratica, è noto come programmazione difensiva ed è considerato una buona pratica di programmazione (1). È usato in abbondanza nei sistemi mission-critical, anche se forse non nella programmazione desktop.

Lo scopo della programmazione difensiva è quello di rilevare errori imprevisti che in teoria non sarebbero mai accaduti. Un tale errore imprevisto non è necessariamente il programmatore che fornisce alla funzione un input errato, o addirittura un "trucco malvagio". Più probabilmente, potrebbe essere causato da una variabile corrotta: overflow del buffer, overflow dello stack, codice di fuga e bug simili non correlati alla tua funzione potrebbero causare questo. E nel caso di sistemi embedded, le variabili potrebbero cambiare a causa di EMI, in particolare se si utilizzano circuiti RAM esterni.

Per quanto riguarda ciò che scrivi all'interno dell'istruzione di default ... se sospetti che il programma sia andato in tilt dopo che sei finito lì, allora hai bisogno di una sorta di gestione degli errori. In molti casi puoi probabilmente semplicemente aggiungere una frase vuota con un commento: "inaspettato ma non importa" ecc., Per mostrare che hai pensato a una situazione improbabile.

(1) MISRA-C: 2004 15.3.

    
risposta data 13.12.2012 - 16:19
fonte
4

Meglio ancora:

typedef enum {
    MaskValueUno,
    MaskValueDos,

    MaskValue_count
} testingMask;

void myFunction(testingMask theMask) {
    assert(theMask >= 0 && theMask<MaskValue_count);
    switch theMask {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
};

Questo è meno soggetto ad errori quando si aggiungono elementi all'enumerazione. Puoi saltare il test per > = 0 se rendi i tuoi valori enum non firmati. Questo metodo funziona solo se non hai vuoti nei tuoi valori enum, ma questo è spesso il caso.

    
risposta data 13.12.2012 - 19:29
fonte
2

Inoltre, preferisco avere un default: in tutti i casi. Sono in ritardo alla festa come al solito, ma ... altri pensieri che non ho visto sopra:

  • Il particolare avviso (o errore se anche il lancio -Werror ) proviene da -Wcovered-switch-default (da -Weverything ma non -Wall ). Se la tua flessibilità morale ti consente di disattivare off alcuni avvisi (ad esempio, eliminare alcune cose da -Wall o -Weverything ), considera il lancio di -Wno-covered-switch-default (o -Wno-error=covered-switch-default quando si utilizza -Werror ) e in generale -Wno-... per altri avvisi che ritieni spiacevoli.
  • Per gcc (e più comportamento generico in clang ), consultare gcc manpage per -Wswitch , -Wswitch-enum , -Wswitch-default per comportamento (diverso) in situazioni simili di tipi enumerati nelle istruzioni switch.
  • Inoltre non mi piace questo avvertimento nel concetto, e non mi piace la sua formulazione; per me, le parole dell'avviso ("etichetta predefinita ... copre tutti i ... valori") suggeriscono che il caso default: sarà sempre eseguito, come ad esempio

    switch (foo) {
      case 1:
        do_something(); 
        //note the lack of break (etc.) here!
      default:
        do_default();
    }
    

    In prima lettura, questo è quello che pensavo stavi incontrando - che il tuo caso default: sarebbe sempre eseguito perché non c'è break; o return; o simile. Questo concetto è simile (al mio orecchio) ad un altro commento di tipo babysitter (anche se occasionalmente utile) che sputa fuori da clang . Se foo == 1 , entrambe le funzioni saranno eseguite; il tuo codice sopra ha questo comportamento. Io, non riesco a scoppiare solo se vuoi continuare a eseguire codice dai casi successivi! Questo sembra, tuttavia, non essere il tuo problema.

A rischio di essere pedante, altri pensieri per la completezza:

  • Tuttavia ritengo che questo comportamento sia (più) coerente con il controllo di tipo aggressivo in altri linguaggi o compilatori. Se, come si ipotizza, alcuni non riescono tentano di passare un int o qualcosa a questa funzione, che intende esplicitamente utilizzare il proprio tipo specifico, il compilatore dovrebbe proteggerti ugualmente bene in quella situazione con un avvertimento o errore aggressivo. MA no! (Cioè, sembra che almeno gcc e clang non facciano enum type-checking, ma Ho sentito che icc fa ). Dal momento che non hai tipo -sicurezza, potresti ottenere value -safety come discusso sopra. Altrimenti, come suggerito in TFA, considera un struct o qualcosa che possa fornire la sicurezza del tipo.
  • Un'altra soluzione alternativa potrebbe essere la creazione di un nuovo "valore" nel enum come MaskValueIllegal e non il supporto di case nel switch . Questo sarebbe mangiato dal default: (in aggiunta a qualsiasi altro valore stravagante)

Lunga vita alla codifica difensiva!

    
risposta data 03.06.2014 - 17:53
fonte
1

But what if some hack comes along and throws an uninitialized int into my beautiful function?

Quindi UB e il tuo default non hanno significato. Non c'è niente che tu possa fare per migliorarlo.

Modifica: vorrei essere più chiaro. L'istante in cui qualcuno passa un int non inizializzato nella tua funzione, è UB. La tua funzione potrebbe risolvere il problema dell'arresto e non avrebbe importanza. È UB. Non c'è nulla che tu possa fare una volta che UB è stato invocato.

    
risposta data 13.12.2012 - 14:09
fonte
0

L'istruzione predefinita non aiuterebbe necessariamente. Se lo switch è su un enum, qualsiasi valore che non è definito nell'enum finirà per eseguire un comportamento indefinito.

Per quanto ne sappia, il compilatore può compilare quell'interruttore (con il valore predefinito) come:

if (theMask == MaskValueUno)
  // Execute something MaskValueUno code
else // theMask == MaskValueDos
  // Execute MaskValueDos code

Una volta attivato il comportamento indefinito, non si può tornare indietro.

    
risposta data 13.12.2012 - 18:45
fonte
0

Ecco un suggerimento alternativo:
L'OP sta cercando di proteggere dal caso in cui qualcuno passa in un int in cui è previsto un enum . O, più probabilmente, dove qualcuno ha collegato una vecchia libreria con un nuovo programma usando una nuova intestazione con più casi.

Perché non cambiare lo switch per gestire il caso int ? L'aggiunta di un cast davanti al valore nello switch elimina l'avviso e fornisce anche un suggerimento sul perché esiste l'impostazione predefinita.

void myFunction(testingMask theMask) {
    int maskValue = int(theMask);
    switch(maskValue) {
        case MaskValueUno: {} // deal with it
        case MaskValueDos: {}// deal with it
        default: {} //deal with an unexpected or uninitialized value
    }
}

Trovo che questo molto sia meno discutibile di assert() testando ciascuno dei possibili valori o anche facendo supporre che l'intervallo di valori enum sia ben ordinato quindi che un test più semplice funziona. Questo è solo un brutto modo di fare ciò che il default fa esattamente e splendidamente.

    
risposta data 08.02.2013 - 19:29
fonte

Leggi altre domande sui tag