Qual è il punto di Noreturn?


189

[dcl.attr.noreturn] fornisce il seguente esempio:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

ma non capisco qual è il punto [[noreturn]], perché il tipo di ritorno della funzione è già void.

Quindi, qual è il punto noreturndell'attributo? Come dovrebbe essere usato?


1
Cosa c'è di così importante in questo tipo di funzionalità (che molto probabilmente accadrà una volta durante l'esecuzione di un programma) che merita tale attenzione? Non è una situazione facilmente rilevabile?
user666412

1
@MrLister L'OP combina i concetti di "ritorno" e "valore di ritorno". Dato che sono quasi sempre usati in tandem, penso che la confusione sia giustificata.
Slipp D. Thompson,

Risposte:


209

L'attributo noreturn dovrebbe essere usato per funzioni che non ritornano al chiamante. Ciò non significa funzioni nulle (che ritornano al chiamante - semplicemente non restituiscono un valore), ma funzioni in cui il flusso di controllo non tornerà alla funzione chiamante al termine della funzione (ad es. Funzioni che escono dall'applicazione, loop per sempre o generare eccezioni come nel tuo esempio).

Questo può essere utilizzato dai compilatori per apportare alcune ottimizzazioni e generare avvisi migliori. Ad esempio, se fha l'attributo noreturn, il compilatore potrebbe avvisarti di g()essere codice morto durante la scrittura f(); g();. Allo stesso modo il compilatore saprà di non avvisarti della mancanza di dichiarazioni di reso dopo le chiamate a f().


5
Che dire di una funzione come execvequella non dovrebbe tornare ma potrebbe ? Dovrebbe avere l' attributo noreturn ?
Kalrish,

22
No, non dovrebbe - se esiste la possibilità che il flusso di controllo ritorni al chiamante, non deve avere l' noreturnattributo. noreturnpuò essere utilizzato solo se la tua funzione è garantita per fare qualcosa che termina il programma prima che il flusso di controllo possa tornare al chiamante, ad esempio perché chiami exit (), abort (), assert (0), ecc.
RavuAlHemio

7
@ SlippD.Thompson Se una chiamata a una funzione noreturn viene racchiusa in un blocco try, qualsiasi codice dal blocco catch attivo verrà conteggiato di nuovo raggiungibile.
sepp2k,

2
@ sepp2k Cool. Quindi non è impossibile tornare, solo anormale. Questo è utile Saluti.
Slipp D. Thompson,

7
@ SlippD. Thompson no, è impossibile tornare. Lanciare un'eccezione non sta tornando, quindi se ogni percorso lancia allora lo è noreturn. La gestione di tale eccezione non è la stessa della sua restituzione. Qualsiasi codice nel trydopo la chiamata è ancora irraggiungibile e, in caso contrario, voidqualsiasi assegnazione o utilizzo del valore restituito non avverrà.
Jon Hanna,

63

noreturnnon dice al compilatore che la funzione non restituisce alcun valore. Indica al compilatore che il flusso di controllo non tornerà al chiamante . Ciò consente al compilatore di apportare una varietà di ottimizzazioni: non è necessario salvare e ripristinare alcuno stato volatile attorno alla chiamata, può eliminare in codice morto qualsiasi codice che altrimenti seguirebbe la chiamata, ecc.


29

Significa che la funzione non verrà completata. Il flusso di controllo non colpirà mai l'istruzione dopo la chiamata a f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

Le informazioni possono essere utilizzate dal compilatore / ottimizzatore in diversi modi. Il compilatore può aggiungere un avviso che il codice sopra riportato non è raggiungibile e può modificare il codice effettivo g()in diversi modi, ad esempio per supportare le continuazioni.



4
@TemplateRex: compila con -Wno-returne riceverai un avviso. Probabilmente non quello che ti aspettavi, ma è probabilmente sufficiente dirti che il compilatore ha conoscenza di ciò che [[noreturn]]è e può trarne vantaggio. (Sono un po 'sorpreso che -Wunreachable-codenon sia
entrato

3
@TemplateRex: Siamo spiacenti -Wmissing-noreturn, l'avviso implica che l'analisi del flusso ha determinato che std::coutnon è raggiungibile. Non ho a disposizione un nuovo CCG sufficiente per esaminare l'assemblaggio generato, ma non sarei sorpreso se la chiamata a operator<<venisse interrotta
David Rodríguez - dribeas,

1
Ecco un dump dell'assembly (-S -o - flags in coliru), in effetti lascia cadere il codice "non raggiungibile". È interessante notare che -O1è già sufficiente rilasciare quel codice irraggiungibile senza il [[noreturn]]suggerimento.
TemplateRex

2
@TemplateRex: tutto il codice è nella stessa unità di traduzione e visibile, quindi il compilatore può dedurlo [[noreturn]]dal codice. Se questa unità di traduzione avesse solo una dichiarazione della funzione definita da qualche altra parte, il compilatore non sarebbe in grado di eliminare quel codice, poiché non sa che la funzione non ritorna. È qui che l'attributo dovrebbe aiutare il compilatore.
David Rodríguez - dribeas,

18

Le risposte precedenti spiegavano correttamente cos'è noreturn, ma non perché esiste. Non penso che i commenti di "ottimizzazione" siano lo scopo principale: le funzioni che non restituiscono sono rare e di solito non hanno bisogno di essere ottimizzate. Piuttosto, penso che la principale ragion d'essere di Noreturn sia quella di evitare allarmi falsi positivi. Ad esempio, considera questo codice:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

Se abort () non fosse stato contrassegnato come "noreturn", il compilatore avrebbe potuto avvertire che questo codice aveva un percorso in cui f non restituiva un numero intero come previsto. Ma poiché abort () è contrassegnato da nessun ritorno, sa che il codice è corretto.


Tutti gli altri esempi elencati utilizzano funzioni void: come funziona quando si dispone sia della direttiva [[no return]] che di un tipo restituito non vuoto? La direttiva [[no return]] entra in gioco solo quando il compilatore è pronto ad avvisare della possibilità di non tornare e ignorare l'avvertimento? Ad esempio, il compilatore dice: "Ok, ecco una funzione non nulla". * continua la compilazione * "Oh merda, questo codice potrebbe non tornare! Devo avvisare l'utente? *" Non importa, vedo la direttiva no-return. Continua "
Raleigh L.

2
La funzione noreturn nel mio esempio non è f (), è abort (). Non ha senso contrassegnare una funzione non nulla né tornare indietro. Una funzione che a volte restituisce un valore e talvolta restituisce (un buon esempio è execve ()) non può essere contrassegnata né restituita.
Nadav Har'El,


12

Digitare teoricamente parlando, voidè ciò che viene chiamato in altre lingue unito top. Il suo equivalente logico è True . È possibile eseguire il cast legittimo di qualsiasi valore void(ogni tipo è un sottotipo di void). Pensalo come un set di "universo"; non ci sono operazioni in comune con tutti i valori nel mondo, quindi non ci sono operazioni valide su un valore di tipo void. Detto in altro modo, dirti che qualcosa appartiene all'insieme dell'universo non ti dà alcuna informazione - lo sai già. Quindi è il seguente suono:

(void)5;
(void)foo(17); // whatever foo(17) does

Ma il seguente incarico non è:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]], D'altra parte, è chiamato a volte empty, Nothing, Bottomo Boted è l'equivalente logico di False . Non ha alcun valore e un'espressione di questo tipo può essere lanciata su (cioè è il sottotipo di) qualsiasi tipo. Questo è l'insieme vuoto. Nota che se qualcuno ti dice "il valore dell'espressione foo () appartiene all'insieme vuoto" è altamente informativo - ti dice che questa espressione non completerà mai la sua normale esecuzione; si interromperà, lancerà o si bloccherà. È l'esatto contrario di void.

Quindi non ha senso quanto segue (pseudo-C ++, poiché noreturnnon è un tipo C ++ di prima classe)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

Ma il seguente incarico è perfettamente legittimo, poiché throwil compilatore è inteso a non restituire:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

In un mondo perfetto, potresti usare noreturncome valore di ritorno per la funzione raise()sopra:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

Purtroppo C ++ non lo consente, probabilmente per motivi pratici. Ti dà invece la possibilità di utilizzare l' [[ noreturn ]]attributo che aiuta a guidare le ottimizzazioni e gli avvisi del compilatore.


5
Nulla può essere lanciato voide voidmai valutato trueo falseo altro.
Più sicuro il

6
Quando dico true, non intendo "il valore truedel tipo bool" ma il senso logico, vedi corrispondenza Curry-Howard
Elazar

8
La teoria dei tipi astratta che non si adatta al sistema di tipi di un linguaggio specifico è irrilevante quando si discute del sistema di tipi di quel linguaggio. La domanda, in questione :-), riguarda il C ++, non la teoria dei tipi.
Più sicuro il

8
(void)true;è perfettamente valido, come suggerisce la risposta. void(true)è qualcosa di completamente diverso, sintatticamente. È un tentativo di creare un nuovo oggetto di tipo voidchiamando un costruttore truecome argomento; questo fallisce, tra l'altro, perché voidnon è di prima classe.
Elazar,

2
Così è. Sono abituato a usare il casting di tipo (valore), non il casting in stile c.
Più sicuro il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.