Compilare la dichiarazione di ritorno mancante in un metodo non vuoto


189

Ho riscontrato una situazione in cui in un metodo non vuoto manca un'istruzione return e il codice viene ancora compilato. So che le dichiarazioni dopo il ciclo while sono irraggiungibili (codice morto) e non verrebbero mai eseguite. Ma perché il compilatore non avverte nemmeno di restituire qualcosa? O perché una lingua ci permetterebbe di avere un metodo non vuoto che ha un ciclo infinito e non restituisce nulla?

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Se aggiungo un'istruzione break (anche se condizionale) nel ciclo while, il compilatore si lamenta degli errori infami: Method does not return a valuein Eclipse e Not all code paths return a valueVisual Studio.

public int doNotReturnAnything() {
    while(true) {
        if(mustReturn) break;
        //do something
    }
    //no return statement
}

Questo vale sia per Java che per C #.


3
Bella domanda Sarei interessato al motivo di questo.
Erik Schierboom,

18
Un'ipotesi: è un ciclo infinito, quindi un flusso di controllo del ritorno è irrilevante?
Lews Therin,

5
"Perché una lingua dovrebbe permetterci di avere un metodo non vuoto che ha un ciclo infinito e non restituisce nulla?" <- mentre apparentemente stupido, la domanda potrebbe anche essere invertita: perché non dovrebbe essere permesso? A proposito, questo codice è vero?
fge,

3
Per Java, puoi trovare una bella spiegazione in questa risposta .
Keppil,

4
Come altri hanno spiegato, è perché il compilatore è abbastanza intelligente da sapere che il ciclo è infinito. Si noti tuttavia che il compilatore non solo consente di perdere il ritorno, ma lo impone perché sa qualcosa dopo che il ciclo è irraggiungibile. Almeno in Netbeans, si lamenterà letteralmente unreachable statementse c'è qualcosa dopo il ciclo.
Supr,

Risposte:


240

Perché una lingua dovrebbe permetterci di avere un metodo non vuoto che ha un ciclo infinito e non restituisce nulla?

La regola per i metodi non nulli è che ogni percorso di codice che restituisce deve restituire un valore e tale regola è soddisfatta nel programma: zero percorsi di codice zero su zero che restituiscono restituiscono un valore. La regola non è "ogni metodo non vuoto deve avere un percorso di codice che ritorni".

Ciò consente di scrivere metodi stub come:

IEnumerator IEnumerable.GetEnumerator() 
{ 
    throw new NotImplementedException(); 
}

Questo è un metodo non vuoto. Esso deve essere un metodo non nullo per soddisfare l'interfaccia. Ma sembra sciocco rendere illegale questa implementazione perché non restituisce nulla.

Che il tuo metodo abbia un punto di arrivo irraggiungibile a causa di un goto(ricorda, a while(true)è solo un modo più piacevole di scrivere goto) invece di un throw(che è un'altra forma di goto) non è rilevante.

Perché il compilatore non avverte nemmeno di restituire qualcosa?

Perché il compilatore non ha buone prove che il codice sia sbagliato. Qualcuno ha scritto while(true)e sembra probabile che la persona che lo ha fatto sapesse cosa stavano facendo.

Dove posso leggere di più sull'analisi di raggiungibilità in C #?

Vedi i miei articoli sull'argomento, qui:

ATBG: raggiungibilità di fatto e di diritto

E potresti anche prendere in considerazione la lettura della specifica C #.


Quindi perché questo codice dà un errore di compilazione: anche public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } questo codice non ha un punto di interruzione. Quindi ora come il compilatore sa che il codice è sbagliato.
Sandeep Poonia,

@SandeepPoonia: potresti leggere qui le altre risposte ... Il compilatore può rilevare solo determinate condizioni.
Daniel Hilgarth,

9
@SandeepPoonia: il flag booleano può essere modificato in fase di esecuzione, poiché non è una const, quindi il loop non è necessariamente infinito.
Schmuli,

9
@SandeepPoonia: In C # la specifica dice che if, while, for, switch, ecc, ramificazione costrutti che operano su costanti sono trattati come rami incondizionati da parte del compilatore. La definizione esatta di espressione costante è nelle specifiche. Le risposte alle tue domande sono nelle specifiche; il mio consiglio è di leggerlo.
Eric Lippert,

1
Every code path that returns must return a valueMigliore risposta. Risolve chiaramente entrambe le domande. Grazie
cPu1

38

Il compilatore Java è abbastanza intelligente da trovare il codice non raggiungibile (il codice dopo il whileciclo)

e dal momento che è irraggiungibile , non ha senso aggiungere una returndichiarazione lì (dopo la whilefine)

lo stesso vale per condizionale if

public int get() {
   if(someBoolean) {   
     return 10;
   }
   else {
     return 5;
   }
   // there is no need of say, return 11 here;
}

poiché la condizione booleana someBooleanpuò solo valutare trueo false, non è necessario fornire un dopo return esplicitoif-else , poiché quel codice è irraggiungibile e Java non se ne lamenta.


4
Non penso che questo affronti davvero la domanda. Ciò risponde al motivo per cui non è necessaria returnun'istruzione nel codice non raggiungibile, ma non ha nulla a che fare con il motivo per cui non è necessaria alcuna return istruzione nel codice dell'OP.
Bobson

Se il compilatore Java è abbastanza intelligente da trovare il codice non raggiungibile (il codice dopo il ciclo while), allora perché questi codici di seguito vengono compilati, entrambi hanno un codice non raggiungibile ma il metodo con se richiede un'istruzione return ma il metodo con un po 'di tempo no. public int doNotReturnAnything() { if(true){ System.exit(1); } return 11; } public int doNotReturnAnything() { while(true){ System.exit(1); } return 11;// Compiler error: unreachable code }
Sandeep Poonia,

@SandeepPoonia: Perché il compilatore non sa che System.exit(1)ucciderà il programma. Può rilevare solo determinati tipi di codice non raggiungibile.
Daniel Hilgarth,

@Daniel Hilgarth: Ok, il compilatore non sa System.exit(1)che ucciderà il programma, possiamo usarne uno return statement, ora il compilatore riconosce il return statements. E il comportamento è lo stesso, il ritorno richiede per if conditionma non per while.
Sandeep Poonia,

1
Buona dimostrazione della sezione irraggiungibile senza utilizzare un caso speciale comewhile(true)
Matteo

17

Il compilatore sa che il whileciclo non smetterà mai di essere eseguito, quindi il metodo non finirà mai, quindi returnun'istruzione non è necessaria.


13

Dato che il tuo ciclo si sta eseguendo su una costante - il compilatore sa che è un ciclo infinito - il che significa che il metodo non potrebbe mai tornare, comunque.

Se si utilizza una variabile, il compilatore applica la regola:

Questo non verrà compilato:

// Define other methods and classes here
public int doNotReturnAnything() {
    var x = true;

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}

Ma cosa succede se "fare qualcosa" non comporta la modifica di x in alcun modo .. il compilatore non è così intelligente da capirlo? Bum :(
Lews Therin,

No - non sembra.
Dave Bish,

@Lews se hai un metodo contrassegnato come restituito int, ma in realtà non ritorna, allora dovresti essere felice che il compilatore lo contrassegni, quindi puoi contrassegnare il metodo come nullo o correggerlo se non lo intendevi comportamento.
MikeFHay,

@MikeFHay Sì, non contestarlo.
Lews Therin,

1
Potrei sbagliarmi, ma alcuni debugger consentono la modifica delle variabili. Qui mentre x non è modificato dal codice e sarà ottimizzato da JIT, si potrebbe modificare x in false e il metodo dovrebbe restituire qualcosa (se tale cosa è consentita dal debugger C #).
Maciej Piechotka,

11

La specifica Java definisce un concetto chiamato Unreachable statements. Non ti è consentito avere un'istruzione irraggiungibile nel tuo codice (è un errore di compilazione). Non ti è nemmeno permesso di avere una dichiarazione di ritorno dopo il tempo (vero); dichiarazione in Java. Un while(true);comunicato rende le seguenti dichiarazioni irraggiungibile per definizione, quindi non hai bisogno di una returndichiarazione.

Si noti che mentre il problema di Halting è indecidibile nel caso generico, la definizione di Dichiarazione irraggiungibile è più rigorosa della semplice interruzione. Sta decidendo casi molto specifici in cui un programma sicuramente non si ferma. Il compilatore non è teoricamente in grado di rilevare tutti i loop infiniti e le dichiarazioni non raggiungibili, ma deve rilevare casi specifici definiti nelle specifiche (ad esempio, il while(true)caso)


7

Il compilatore è abbastanza intelligente da scoprire che il tuo whileloop è infinito.

Quindi il compilatore non può pensare per te. Non può indovinare il motivo per cui hai scritto quel codice. Lo stesso vale per i valori di ritorno dei metodi. Java non si lamenterà se non si fa nulla con i valori di ritorno del metodo.

Quindi, per rispondere alla tua domanda:

Il compilatore analizza il tuo codice e dopo aver scoperto che nessun percorso di esecuzione porta alla caduta della fine della funzione, termina con OK.

Potrebbero esserci motivi legittimi per un ciclo infinito. Ad esempio molte app usano un ciclo principale infinito. Un altro esempio è un server Web che può attendere indefinitamente le richieste.


7

Nella teoria dei tipi, c'è qualcosa chiamato il tipo di fondo che è una sottoclasse di ogni altro tipo (!) E viene usato per indicare la non terminazione tra le altre cose. (Le eccezioni possono essere considerate come un tipo di non terminazione: non si termina tramite il percorso normale.)

Quindi da un punto di vista teorico, queste affermazioni che non sono terminanti possono essere considerate come qualcosa di tipo Bottom, che è un sottotipo di int, quindi (in qualche modo) ottieni il tuo valore di ritorno da una prospettiva di tipo. Ed è perfettamente ok che non ha alcun senso che un tipo possa essere una sottoclasse di tutto il resto incluso int perché non lo restituisci mai realmente.

In ogni caso, tramite la teoria dei tipi esplicita o meno, i compilatori (autori di compilatori) riconoscono che richiedere un valore restituito dopo un'istruzione non terminante è sciocco: non esiste un caso possibile in cui potresti aver bisogno di quel valore. (Può essere bello che il tuo compilatore ti avverta quando sa che qualcosa non si chiuderà, ma sembra che tu voglia che restituisca qualcosa. Ma è meglio lasciarlo per un controllo di stile, dato che forse hai bisogno della firma del tipo che per qualche altro motivo (ad es. sottoclasse) ma si desidera davvero la non terminazione.)


6

Non esiste alcuna situazione in cui la funzione possa raggiungere la sua fine senza restituire un valore appropriato. Pertanto, non c'è nulla di cui lamentarsi per il compilatore.


5

Visual Studio ha il motore intelligente per rilevare se hai digitato un tipo restituito, quindi dovrebbe avere un'istruzione return con nella funzione / metodo.

Come in PHP Il tuo tipo di reso è vero se non hai restituito nulla. il compilatore ottiene 1 se non è tornato nulla.

A partire da questo

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Il compilatore sa che mentre l'affermazione stessa ha una natura infta, quindi non considerarla. e il compilatore php diventerà automaticamente vero se scrivi una condizione nell'espressione di while.

Ma non nel caso di VS ti restituirà un errore nello stack.


4

Il tuo ciclo while funzionerà per sempre e quindi non verrà fuori mentre; continuerà a essere eseguito. Quindi, la parte esterna di while {} è irraggiungibile e non ha senso scrivere o meno il ritorno. Il compilatore è abbastanza intelligente da capire quale parte è raggiungibile e quale parte non lo è.

Esempio:

public int xyz(){
    boolean x=true;

    while(x==true){
        // do something  
    }

    // no return statement
}

Il codice sopra non verrà compilato, perché potrebbe esserci un caso in cui il valore della variabile x viene modificato all'interno del corpo del ciclo while. Quindi questo rende raggiungibile la parte esterna di while loop! E quindi il compilatore genererà un errore "nessuna dichiarazione di ritorno trovata".

Il compilatore non è abbastanza intelligente (o piuttosto pigro;)) per capire se il valore di x verrà modificato o meno. Spero che questo cancella tutto.


7
In realtà in questo caso non è una questione di compilatore abbastanza intelligente. In C # 2.0 il compilatore era abbastanza intelligente da sapere che int x = 1; while(x * 0 == 0) { ... }era un ciclo infinito, ma la specifica dice che il compilatore dovrebbe fare deduzioni del flusso di controllo solo quando l'espressione del ciclo è costante e la specifica definisce un'espressione costante come non contenente variabili . Il compilatore era quindi troppo intelligente . In C # 3 ho fatto in modo che il compilatore corrispondesse alle specifiche e da allora è volutamente meno intelligente su questo tipo di espressioni.
Eric Lippert,

4

"Perché il compilatore non avverte nemmeno di restituire qualcosa? O perché una lingua ci consentirebbe di avere un metodo non vuoto che ha un ciclo infinito e non restituisce nulla?".

Questo codice è valido anche in tutte le altre lingue (probabilmente tranne Haskell!). Perché il primo presupposto è che stiamo "intenzionalmente" scrivendo del codice.

E ci sono situazioni in cui questo codice può essere totalmente valido come se lo usassi come thread; o se restituiva un Task<int>, è possibile eseguire un controllo degli errori in base al valore int restituito, che non deve essere restituito.


3

Potrei sbagliarmi, ma alcuni debugger consentono la modifica delle variabili. Qui mentre x non è modificato dal codice e sarà ottimizzato da JIT, si potrebbe modificare x in false e il metodo dovrebbe restituire qualcosa (se tale cosa è consentita dal debugger C #).


1

Le specifiche del caso Java per questo (che probabilmente sono molto simili al caso C #) hanno a che fare con il modo in cui il compilatore Java determina se un metodo è in grado di restituire.

In particolare, le regole prevedono che un metodo con un tipo di restituzione non deve essere in grado di completarsi normalmente e deve invece sempre completarsi bruscamente (qui in modo brusco indicando un'istruzione di restituzione o un'eccezione) per JLS 8.4.7 .

Se viene dichiarato che un metodo ha un tipo restituito, si verifica un errore di compilazione se il corpo del metodo può essere completato normalmente. In altre parole, un metodo con un tipo restituito deve restituire solo usando un'istruzione return che fornisce un valore restituito; non è permesso "abbandonare la fine del suo corpo" .

Il compilatore cerca di vedere se la terminazione normale è possibile in base alle regole definite in JLS 14.21 Dichiarazioni non raggiungibili in quanto definisce anche le regole per il completamento normale.

In particolare, le regole per le affermazioni irraggiungibili rappresentano un caso speciale solo per i loop che hanno trueun'espressione costante definita :

Un'istruzione while può essere completata normalmente se almeno una delle seguenti condizioni è vera:

  • L'istruzione while è raggiungibile e l'espressione della condizione non è un'espressione costante (§15.28) con valore true.

  • Esiste un'istruzione break raggiungibile che esce dall'istruzione while.

Pertanto, se l' whileistruzione può essere completata normalmente , è necessaria un'istruzione di ritorno di seguito poiché il codice è considerato raggiungibile e qualsiasi whileciclo senza un'istruzione di interruzione raggiungibile o trueun'espressione costante è considerato in grado di completarsi normalmente.

Queste regole indicano che la tua whileaffermazione con un'espressione vera costante e senza a nonbreak viene mai considerata completata normalmente , e quindi qualsiasi codice sottostante non viene mai considerato raggiungibile . La fine del metodo è al di sotto del ciclo e poiché tutto ciò che si trova al di sotto del ciclo è irraggiungibile, così è la fine del metodo e quindi il metodo non può essere completato normalmente (che è ciò che cerca il complier).

if le dichiarazioni, d'altra parte, non hanno l'esenzione speciale per quanto riguarda le espressioni costanti che sono concesse ai cicli.

Confrontare:

// I have a compiler error!
public boolean testReturn()
{
    final boolean condition = true;

    if (condition) return true;
}

Con:

// I compile just fine!
public boolean testReturn()
{
    final boolean condition = true;

    while (condition)
    {
        return true;
    }
}

Il motivo della distinzione è piuttosto interessante ed è dovuto al desiderio di consentire flag di compilazione condizionali che non causano errori del compilatore (dal JLS):

Ci si potrebbe aspettare che l'istruzione if venga gestita nel modo seguente:

  • Un'istruzione if-then può essere completata normalmente iff è vera almeno una delle seguenti condizioni:

    • L'istruzione if-then è raggiungibile e l'espressione della condizione non è un'espressione costante il cui valore è vero.

    • L'istruzione then può essere completata normalmente.

    L'istruzione then è raggiungibile iff l'istruzione if-then è raggiungibile e l'espressione condizione non è un'espressione costante il cui valore è falso.

  • Un'istruzione if-then-else può essere completata normalmente iff l'istruzione then può essere completata normalmente o l'istruzione else può essere completata normalmente.

    • L'istruzione then è raggiungibile se l'istruzione if-then-else è raggiungibile e l'espressione della condizione non è un'espressione costante il cui valore è falso.

    • L'istruzione else è raggiungibile se l'istruzione if-then-else è raggiungibile e l'espressione della condizione non è un'espressione costante il cui valore è vero.

Questo approccio sarebbe coerente con il trattamento di altre strutture di controllo. Tuttavia, al fine di consentire l'utilizzo dell'istruzione if in modo conveniente per scopi di "compilazione condizionale", le regole effettive differiscono.

Ad esempio, la seguente istruzione genera un errore in fase di compilazione:

while (false) { x=3; }perché la dichiarazione x=3;non è raggiungibile; ma il caso superficialmente simile:

if (false) { x=3; }non provoca un errore di compilazione. Un compilatore di ottimizzazione può rendersi conto che l'istruzione x=3;non verrà mai eseguita e può scegliere di omettere il codice per quell'istruzione dal file di classe generato, ma l'istruzione x=3;non è considerata "irraggiungibile" nel senso tecnico qui specificato.

La logica di questo diverso trattamento è quella di consentire ai programmatori di definire "variabili flag" come:

static final boolean DEBUG = false; e quindi scrivere codice come:

if (DEBUG) { x=3; } L'idea è che dovrebbe essere possibile cambiare il valore di DEBUG da falso a vero o da vero a falso e quindi compilare il codice correttamente senza altre modifiche al testo del programma.

Perché l'istruzione break condizionale provoca un errore del compilatore?

Come indicato nelle regole di raggiungibilità del ciclo, un ciclo while può anche completarsi normalmente se contiene un'istruzione break raggiungibile. Dal momento che le regole per la raggiungibilità di ifun'affermazione allora una clausola non prendono affatto ifin considerazione la condizione ifdell'affermazione, tale clausola allora di un'istruzione condizionale è sempre considerata raggiungibile.

Se breakè raggiungibile, anche il codice dopo il ciclo viene nuovamente considerato raggiungibile. Poiché non esiste un codice raggiungibile che comporti la chiusura improvvisa dopo il ciclo, il metodo viene quindi considerato in grado di completarsi normalmente, quindi il compilatore lo contrassegna come un errore.

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.