6

Quando compilo il seguente codice allo standard C++ 11, funziona bene con clang, e anche con gcc, ma gcc (tutte le versioni che ho testato 4.8.2, 4.9.2, 5.1.0) danno un avvertimento:gcc: come gestire al meglio gli avvisi relativi alla fine della funzione (irraggiungibile) dopo l'interruttore?

#include <iostream> 

enum class FOO { A, B, C }; 

const char * bar(FOO f) { 
    switch (f) { 
    case FOO::A: 
     return "A"; 
    case FOO::B: 
     return "B"; 
    case FOO::C: 
     return "C"; 
    } 
} 

int main() { 
    unsigned int x; 
    std::cin >> x; 

    FOO f = static_cast<FOO>(x % 3); 
    std::cout << bar(f) << std::endl; 
} 

l'avvertimento è -Wreturn-type:

main.cpp: In function ‘const char* bar(FOO)’: 
main.cpp:14:1: error: control reaches end of non-void function [-Werror=return-type] 
} 
^ 
cc1plus: all warnings being treated as errors 

ho ancora ottenere l'avvertimento anche con -O2 o -O3 ottimizzazioni - questo significa che anche ad alti livelli di ottimizzazione, gcc non può dead-code eliminare il ' fine 'della funzione?

In particolare, non mi dà l'avviso su casi di interruttori non gestiti.

Modifica: dagli esperimenti con Godbolt, sembra che anche a livelli elevati, non elimina il codice. Non sono sicuro che sia o se clang fa per esempio.

C'è un buon modo per sopprimere localmente questo avviso all'interno di tale funzione, o è l'unico modo per sopprimerlo per disabilitare l'avviso in generale?

Edit: Credo che la questione presenta un linguaggio naturale domanda avvocato, a giudicare dalle risposte:

Può un compilatore conforme morto codice di eliminare la "fine" della funzione bar la proposta? (Oppure la versione 101010 con return nullptr; è stata aggiunta?) O conforme allo standard richiede che generi codice per gestire i valori enum che non fanno parte della definizione enum?

La mia convinzione era che il codice potrebbe eliminarlo, ma è il caso di dimostrarmi in errore.

+0

Il problema è che 'bar' è dichiarato per restituire un' char * 'ma non nel caso' default'. – Kenney

+0

Ne sei così sicuro? Lo standard in particolare consente di non restituire nulla da main. Aggiungendo 'return 0;' alla fine non si ferma l'avvertimento, e l'avvertimento si riferisce comunque alla funzione 'bar' in ogni caso. –

+0

Hai ragione, mi dispiace, è solo la funzione della barra. – Kenney

risposta

8

significa che anche con alti livelli di ottimizzazione, gcc non può dead-code eliminare il 'fine' della funzione?

Sì, perché non è un codice guasto.

Lo standard consente di chiamare la propria funzione con static_cast<FOO>(static_cast<int>(FOO::B) | static_cast<int>(FOO::C)) come argomento. Il tuo interruttore non gestisce questo.

Il motivo per cui non viene visualizzato un avviso relativo a ciò che non viene gestito nel proprio switch è perché l'avviso avrebbe modo troppi positivi.

+0

'static_cast (statico_cast (FOO :: B) | static_cast (FOO :: C))' [comportamento non definito] (http: // stackoverflow. com/q/18195312/3425 mila cinquecentotrentasei). – emlai

+1

@zenith Nope, 'FOO' ha un tipo fisso sottostante' int', quindi "i valori dell'enumerazione sono i valori del tipo sottostante", ed è ben definito per castare tutto tra 'INT_MIN' e' INT_MAX' in ' foo'. –

+3

Sarebbe definito anche se 'FOO' non aveva un tipo sottostante fisso. L'intervallo di * any * enum, con o senza tipo di base fisso, è sufficiente per coprire l'OR bit a bit di qualsiasi valore di enumerazione. – hvd

1

Al fine di sopprimere cambiare l'avviso il codice per:

const char * bar(FOO f) { 
    switch (f) { 
    case FOO::A: 
     return "A"; 
    case FOO::B: 
     return "B"; 
    case FOO::C: 
     return "C"; 
    } 

    return nullptr; 
    ^^^^^^^^^^^^^^^ 
} 

Oppure:

const char * bar(FOO f) { 
    switch (f) { 
    case FOO::A: 
     return "A"; 
    case FOO::B: 
     return "B"; 
    case FOO::C: 
     return "C"; 
    default: 
     return nullptr; 
     ^^^^^^^^^^^^^^^ 
    } 
} 

Il compilatore avvisa che non stai tornando nulla nel caso in cui non lo fa f cadere in uno dei casi previsti in switch. Cadere della fine di una funzione non vuota senza tornare introduce un comportamento indefinito.

la fine della funzione della barra non è irraggiungibile. Se io modificare il codice a:

#include <iostream> 

enum class FOO { A, B, C }; 

const char * bar(FOO f) { 
    switch (f) { 
    case FOO::A: 
     return "A"; 
    case FOO::B: 
     return "B"; 
    case FOO::C: 
     return "C"; 
    } 

    std::cout << "End Reached" << std::endl; 
    //return nullptr; 
} 

int main() { 
    unsigned int x = 10; 
    FOO f = static_cast<FOO>(x); 
    std::cout << bar(f) << std::endl; 
} 

uscita:

End Reached 

Live Demo

+0

ok, ho capito, ma più in generale sai perché è necessario? Voglio dire gcc sarà solo dead-code eliminare quel ritorno comunque, giusto? "Il compilatore avverte che non stai restituendo nulla nel caso in cui f non ricada in uno dei casi forniti nello switch." Il compilatore sa che non esiste un caso del genere, giusto? Altrimenti mi darebbe un avvertimento diverso su quello –

+1

Codice Morto è qualcos'altro. Questo riguarda ciò che viene restituito. Il codice guasto è 'if (0) {/ * codice morto * /}'. – Kenney

+0

@Kenney: io non la penso così. Il codice morto riguarda l'eliminazione di dichiarazioni irraggiungibili. 'return nullptr;' è irraggiungibile quindi non dovrebbe importare se lo scrivo o meno. –

1

vorrei aggiungere un assert(false) al termine della funzione:

#include <cassert> 

const char * bar(FOO f) { 
    switch (f) { 
    case FOO::A: 
     return "A"; 
    case FOO::B: 
     return "B"; 
    case FOO::C: 
     return "C"; 
    } 

    assert(false); 
} 

(Si noti che aggiungendolo come il caso default non sarebbe così buono, dal momento che sarebbe sil tutti gli avvisi caso di commutazione non gestiti quando ad es.aggiungendo nuovi valori enum.)

Questo è utile anche per il debug perché consente di sapere immediatamente quando bar riceve un argomento non valido per qualche motivo.

+0

zenith: voglio dire che quello che voglio veramente è un avviso * di compilazione * che un caso di commutazione non è gestito. e GCC lo fa davvero, emettendo l'avvertimento di cambio quando non l'ho fatto. Quindi non so perché ho bisogno di aggiungere un * run-time * per controllare l'errore, se GCC sta già verificando in fase di compilazione che l'ho gestito correttamente? –

+0

Ma il controllo in fase di compilazione non funziona comunque. Si consideri ad es. 'FOO f = static_cast (x% 4);' (4 invece di 3) – emlai

+0

Ho pensato che fosse UB comunque? –

1

Sicuramente ci sono più di un "modo migliore". Questa risposta si avvicina al problema eliminando switch di sorta.

... e questo modo particolare potrebbe non essere quello che ti aspetti. Sto rischiando di essere downvoted, ma sto ancora postando questo. La linea di fondo è: a volte la soluzione "paradigm shift" funziona meglio nell'applicazione data rispetto a qualsiasi soluzione alternativa e disabilitazione dell'avviso.

switch è un noto "odore di codice". Non dico che sia sempre brutto, ma a volte si può fare meglio di cambiare, e vale la pena di prendere in considerazione alternative.

Nel nostro caso, la funzione bar fa traduzione (in senso linguistico). Cerca una chiave nel dizionario e restituisce una parola tradotta. È possibile esplicitare il codice utilizzando contenitori standard. L'esempio classico è std::map (che si chiama "dizionario" in altri linguaggi di programmazione):

#include <map> 

enum class FOO { 
    A, B, C 
}; 

// Might be a vector or even static array if your enumeration is contiguous 
std::map<FOO, std::string> dict = { 
    { FOO::A, "A" }, // maps ints to strings 
    { FOO::B, "B" }, 
    { FOO::C, "C" }, 
}; 

int main() { 
    auto it = dict.find(FOO::A); 
    if (it == dict.cend()) { 
     // Handle "no such key" case 
    } 
    auto a = it->second; // "A" 
} 

Non importa quale soluzione scelta, il problema si pone se non si dispone di qualche chiave nel dizionario. Dovresti comunque gestire questo caso.

Problemi correlati