Instruction de retour manquante dans une méthode non void compile

191

J'ai rencontré une situation dans laquelle une méthode non void manque une déclaration de retour et le code se compile toujours. Je sais que les instructions après la boucle while sont inaccessibles (code mort) et ne seraient jamais exécutées. Mais pourquoi le compilateur ne prévient-il même pas de retourner quelque chose? Ou pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?

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

Si j'ajoute une instruction break (même conditionnelle) dans la boucle while, le compilateur se plaint des erreurs infâmes: Method does not return a valuedans Eclipse et Not all code paths return a valuedans Visual Studio.

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

Cela est vrai à la fois pour Java et C #.

cPu1
la source
3
Bonne question. Je serais intéressé par la raison de cela.
Erik Schierboom
18
Une supposition: c'est une boucle infinie, donc un flux de contrôle de retour n'est pas pertinent?
Lews Therin
5
"pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?" <- bien qu'apparemment stupide, la question pourrait également être inversée: pourquoi cela ne serait-il pas permis? S'agit-il du code réel, au fait?
fge
3
Pour Java, vous pouvez trouver une belle explication dans cette réponse .
Keppil
4
Comme d'autres l'ont expliqué, c'est parce que le compilateur est suffisamment intelligent pour savoir que la boucle est infinie. Notez cependant que le compilateur permet non seulement de manquer le retour, mais il l' applique car il sait quoi que ce soit une fois que la boucle est inaccessible. Au moins dans Netbeans, il se plaindra littéralement de savoir unreachable statements'il y a quelque chose après la boucle.
Supr

Réponses:

241

Pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?

La règle pour les méthodes non-void est que chaque chemin de code qui retourne doit renvoyer une valeur , et cette règle est satisfaite dans votre programme: zéro chemin de code sur zéro qui retourne renvoient une valeur. La règle n'est pas "chaque méthode non-void doit avoir un chemin de code qui retourne".

Cela vous permet d'écrire des méthodes de stub comme:

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

C'est une méthode non nulle. Il doit s'agir d'une méthode non vide pour satisfaire l'interface. Mais il semble ridicule de rendre cette implémentation illégale car elle ne renvoie rien.

Que votre méthode ait un point final inaccessible à cause d'un goto(rappelez-vous, a while(true)est juste une manière plus agréable d'écrire goto) au lieu d'un throw(qui est une autre forme de goto) n'est pas pertinent.

Pourquoi le compilateur ne prévient-il même pas de retourner quelque chose?

Parce que le compilateur n'a aucune bonne preuve que le code est incorrect. Quelqu'un a écrit while(true)et il semble probable que la personne qui a fait cela savait ce qu'elle faisait.

Où puis-je en savoir plus sur l'analyse d'accessibilité en C #?

Voir mes articles sur le sujet, ici:

ATBG: accessibilité de facto et de jure

Et vous pouvez également envisager de lire la spécification C #.

Eric Lippert
la source
Alors pourquoi ce code donne une erreur de compilation: public int doNotReturnAnything() { boolean flag = true; while (flag) { //Do something } //no return } Ce code n'a pas non plus de point d'arrêt. Alors maintenant, comment le compilateur sait que le code est faux.
Sandeep Poonia
@SandeepPoonia: Vous voudrez peut-être lire les autres réponses ici ... Le compilateur ne peut détecter que certaines conditions.
Daniel Hilgarth
9
@SandeepPoonia: l'indicateur booléen peut être changé à l'exécution, car ce n'est pas un const, donc la boucle n'est pas forcément infinie.
Schmuli
9
@SandeepPoonia: Dans C # la spécification dit que if, while, for, switch, etc, constructions qui fonctionnent sur ramification constantes sont traitées comme des branches sans conditions par le compilateur. La définition exacte de l' expression constante est dans la spécification. Les réponses à vos questions se trouvent dans le cahier des charges; mon conseil est que vous le lisez.
Eric Lippert
1
Every code path that returns must return a valueMeilleure réponse. Répond clairement aux deux questions. Merci
cPu1
38

Le compilateur Java est suffisamment intelligent pour trouver le code inaccessible (le code après whileboucle)

et comme il est inaccessible , il ne sert à rien d'y ajouter une returndéclaration (après la whilefin)

il en va de même avec le conditionnel if

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

étant donné que la condition booléenne ne someBooleanpeut être évaluée qu'à l'un trueou l' autre false, il n'est pas nécessaire de fournir un après return explicitementif-else , car ce code est inaccessible et Java ne s'en plaint pas.

sanbhat
la source
4
Je ne pense pas que cela répond vraiment à la question. Cela explique pourquoi vous n'avez pas besoin d'une returninstruction dans un code inaccessible, mais n'a rien à voir avec la raison pour laquelle vous n'avez besoin d' aucune return instruction dans le code de l'OP.
Bobson
Si le compilateur Java est assez intelligent pour trouver le code inaccessible (le code après la boucle while), alors pourquoi ces codes ci-dessous se compilent, les deux ont un code inaccessible mais la méthode avec if nécessite une instruction de retour, mais la méthode avec un while ne le fait pas. 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: Parce que le compilateur ne sait pas, cela System.exit(1)va tuer le programme. Il ne peut détecter que certains types de code inaccessible.
Daniel Hilgarth
@Daniel Hilgarth: Ok le compilateur ne sait pas qu'il System.exit(1)va tuer le programme, nous pouvons en utiliser n'importe lequel return statement, maintenant le compilateur reconnaît le return statements. Et le comportement est le même, le retour exige pour if conditionmais pas pour while.
Sandeep Poonia
1
Bonne démonstration de la section inaccessible sans utiliser un cas particulier tel quewhile(true)
Matthieu
17

Le compilateur sait que la whileboucle n'arrêtera jamais de s'exécuter, donc la méthode ne se terminera jamais, donc une returninstruction n'est pas nécessaire.

Daniel Hilgarth
la source
14

Étant donné que votre boucle s'exécute sur une constante - le compilateur sait que c'est une boucle infinie - ce qui signifie que la méthode ne pourra jamais revenir, de toute façon.

Si vous utilisez une variable, le compilateur appliquera la règle:

Cela ne compilera pas:

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

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}
Dave Bish
la source
Mais que se passe-t-il si "faire quelque chose" n'implique en aucune façon de modifier x ... le compilateur n'est pas si intelligent pour le comprendre? Bum :(
Lews Therin
Non, ça ne ressemble pas à ça.
Dave Bish
@Lews si vous avez une méthode qui est marquée comme retournant un int, mais en fait ne retourne pas, alors vous devriez être heureux que le compilateur le signale, vous pouvez donc marquer la méthode comme nulle ou la corriger si vous ne l'aviez pas prévu comportement.
MikeFHay
@MikeFHay Oui, sans contester cela.
Lews Therin
1
Je me trompe peut-être mais certains débogueurs permettent la modification des variables. Ici, alors que x n'est pas modifié par le code et qu'il sera optimisé par JIT, on pourrait modifier x en false et la méthode devrait retourner quelque chose (si une telle chose est autorisée par le débogueur C #).
Maciej Piechotka
11

La spécification Java définit un concept appelé Unreachable statements. Vous n'êtes pas autorisé à avoir une instruction inaccessible dans votre code (c'est une erreur de compilation). Vous n'êtes même pas autorisé à avoir une déclaration de retour après le while (true); déclaration en Java. Une while(true);instruction rend les instructions suivantes inaccessibles par définition, vous n'avez donc pas besoin d'une returninstruction.

Notez que bien que le problème d'arrêt soit indécidable dans le cas générique, la définition de l'instruction inaccessible est plus stricte que simplement l'arrêt. C'est décider des cas très spécifiques où un programme ne s'arrête définitivement pas. Le compilateur n'est théoriquement pas capable de détecter toutes les boucles infinies et les instructions inaccessibles mais il doit détecter les cas spécifiques définis dans la spécification (par exemple, le while(true)cas)

Konstantin Yovkov
la source
7

Le compilateur est suffisamment intelligent pour découvrir que votre whileboucle est infinie.

Le compilateur ne peut donc pas penser à votre place. Il ne peut pas deviner pourquoi vous avez écrit ce code. Même chose pour les valeurs de retour des méthodes. Java ne se plaindra pas si vous ne faites rien avec les valeurs de retour de la méthode.

Donc, pour répondre à votre question:

Le compilateur analyse votre code et après avoir découvert qu'aucun chemin d'exécution ne conduit à tomber la fin de la fonction, il se termine par OK.

Il peut y avoir des raisons légitimes à une boucle infinie. Par exemple, de nombreuses applications utilisent une boucle principale infinie. Un autre exemple est un serveur Web qui peut attendre indéfiniment des demandes.

Adam Arold
la source
7

Dans la théorie des types, il y a quelque chose appelé le type inférieur qui est une sous-classe de tous les autres types (!) Et est utilisé pour indiquer la non-terminaison entre autres. (Les exceptions peuvent compter comme un type de non-résiliation - vous ne vous arrêtez pas via le chemin normal.)

Donc, d'un point de vue théorique, ces instructions qui ne se terminent pas peuvent être considérées comme retournant quelque chose de type Bottom, qui est un sous-type de int, donc vous obtenez (en quelque sorte) votre valeur de retour après tout du point de vue du type. Et c'est parfaitement normal que cela n'a aucun sens qu'un type puisse être une sous-classe de tout le reste, y compris int, car vous n'en retournez jamais un.

Dans tous les cas, via la théorie des types explicites ou non, les compilateurs (rédacteurs de compilateurs) reconnaissent que demander une valeur de retour après une instruction sans fin est idiot: il n'y a aucun cas possible dans lequel vous pourriez avoir besoin de cette valeur. (Cela peut être bien que votre compilateur vous avertisse quand il sait que quelque chose ne se terminera pas mais il semble que vous vouliez qu'il renvoie quelque chose. Mais c'est mieux de laisser les vérificateurs de style à la lint, car vous avez peut-être besoin de la signature de type qui pour une autre raison (par exemple, sous-classification) mais vous voulez vraiment la non-terminaison.)

Rex Kerr
la source
6

Il n'y a pas de situation dans laquelle la fonction peut atteindre sa fin sans renvoyer une valeur appropriée. Par conséquent, le compilateur n'a rien à redire.

Graham Borland
la source
5

Visual studio a le moteur intelligent pour détecter si vous avez tapé un type de retour, alors il devrait avoir une instruction return avec dans la fonction / méthode.

Comme en PHP Votre type de retour est vrai si vous n'avez rien retourné. le compilateur obtient 1 si rien n'est retourné.

À partir de ce

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

Le compilateur sait que l'instruction while elle-même a une nature infinie afin de ne pas la considérer. et le compilateur php deviendra automatiquement vrai si vous écrivez une condition dans l'expression de while.

Mais pas dans le cas de VS il vous renverra une erreur dans la pile.

Tarun Gupta
la source
4

Votre boucle while fonctionnera pour toujours et par conséquent ne sortira pas pendant; il continuera à s'exécuter. Par conséquent, la partie extérieure de while {} est inaccessible et il n'y a pas de raison d'écrire return ou non. Le compilateur est suffisamment intelligent pour déterminer quelle partie est accessible et quelle partie ne l'est pas.

Exemple:

public int xyz(){
    boolean x=true;

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

    // no return statement
}

Le code ci-dessus ne se compilera pas, car il peut y avoir un cas où la valeur de la variable x est modifiée dans le corps de la boucle while. Cela rend donc la partie extérieure de la boucle while accessible! Et par conséquent, le compilateur lancera une erreur «aucune instruction de retour trouvée».

Le compilateur n'est pas assez intelligent (ou plutôt paresseux;)) pour déterminer si la valeur de x sera modifiée ou non. J'espère que cela efface tout.

kunal18
la source
7
Il ne s'agit en fait pas dans ce cas de savoir si le compilateur est assez intelligent. En C # 2.0, le compilateur était assez intelligent pour savoir qu'il int x = 1; while(x * 0 == 0) { ... }s'agissait d'une boucle infinie, mais la spécification dit que le compilateur ne doit faire des déductions de flux de contrôle que lorsque l'expression de boucle est constante et que la spécification définit une expression constante comme ne contenant aucune variable . Le compilateur était donc trop intelligent . En C # 3, j'ai fait en sorte que le compilateur corresponde à la spécification, et depuis lors, il est délibérément moins intelligent à propos de ce type d'expressions.
Eric Lippert
4

"Pourquoi le compilateur ne prévient-il même pas de renvoyer quelque chose? Ou pourquoi un langage nous permettrait-il d'avoir une méthode non vide ayant une boucle infinie et ne renvoyant rien?".

Ce code est également valable dans toutes les autres langues (probablement sauf Haskell!). Parce que la première hypothèse est que nous écrivons «intentionnellement» du code.

Et il y a des situations où ce code peut être totalement valide comme si vous allez l'utiliser comme thread; ou s'il retournait a Task<int>, vous pourriez effectuer une vérification d'erreur basée sur la valeur int retournée - qui ne devrait pas être retournée.

Kaveh Shahbazian
la source
3

Je me trompe peut-être mais certains débogueurs permettent la modification des variables. Ici, alors que x n'est pas modifié par le code et qu'il sera optimisé par JIT, on pourrait modifier x en false et la méthode devrait retourner quelque chose (si une telle chose est autorisée par le débogueur C #).


la source
1

Les spécificités du cas Java pour cela (qui sont probablement très similaires au cas C #) sont liées à la façon dont le compilateur Java détermine si une méthode est capable de retourner.

Plus précisément, les règles sont qu'une méthode avec un type de retour ne doit pas pouvoir se terminer normalement et doit à la place toujours se terminer brusquement (brusquement ici indiquant via une instruction de retour ou une exception) selon JLS 8.4.7 .

Si une méthode est déclarée avoir un type de retour, une erreur de compilation se produit si le corps de la méthode peut se terminer normalement. En d'autres termes, une méthode avec un type de retour doit renvoyer uniquement en utilisant une instruction return qui fournit un retour de valeur; il n'est pas autorisé à «déposer le bout de son corps» .

Le compilateur cherche à voir si une terminaison normale est possible sur la base des règles définies dans JLS 14.21 Instructions inaccessibles car il définit également les règles pour la complétion normale.

Notamment, les règles pour les instructions inaccessibles font un cas spécial uniquement pour les boucles qui ont une trueexpression constante définie :

Une instruction while peut se terminer normalement si au moins l'une des conditions suivantes est vraie:

  • L'instruction while est accessible et l'expression de condition n'est pas une expression constante (§15.28) avec la valeur true.

  • Il existe une instruction break accessible qui quitte l'instruction while.

Donc, si l' whileinstruction peut se terminer normalement , une instruction return ci-dessous est nécessaire car le code est considéré comme accessible, et toute whileboucle sans instruction break ou constante accessibletrue expression est considérée comme capable de se terminer normalement.

Ces règles signifient que votre whileinstruction avec une expression vraie constante et sans a breakn'est jamais considérée comme terminée normalement , et donc tout code en dessous n'est jamais considéré comme accessible . La fin de la méthode est en dessous de la boucle, et comme tout ce qui se trouve en dessous de la boucle est inaccessible, la fin de la méthode l'est aussi, et donc la méthode ne peut pas se terminer normalement (ce que recherche le complicateur).

if les instructions, en revanche, ne bénéficient pas de l'exemption spéciale concernant les expressions constantes accordées aux boucles.

Comparer:

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

    if (condition) return true;
}

Avec:

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

    while (condition)
    {
        return true;
    }
}

La raison de la distinction est assez intéressante, et est due au désir de permettre des indicateurs de compilation conditionnelle qui ne provoquent pas d'erreurs de compilation (à partir du JLS):

On peut s'attendre à ce que l'instruction if soit gérée de la manière suivante:

  • Une instruction if-then peut se terminer normalement ssi au moins l'une des conditions suivantes est vraie:

    • L'instruction if-then est accessible et l'expression de condition n'est pas une expression constante dont la valeur est true.

    • L'instruction then peut se terminer normalement.

    L'instruction then est accessible ssi l'instruction if-then est accessible et l'expression de condition n'est pas une expression constante dont la valeur est fausse.

  • Une instruction if-then-else peut se terminer normalement si l'instruction then peut se terminer normalement ou si l'instruction else peut se terminer normalement.

    • L'instruction then est accessible ssi l'instruction if-then-else est accessible et l'expression de condition n'est pas une expression constante dont la valeur est fausse.

    • L'instruction else est accessible ssi l'instruction if-then-else est accessible et l'expression de condition n'est pas une expression constante dont la valeur est true.

Cette approche serait cohérente avec le traitement des autres structures de contrôle. Cependant, afin de permettre l'utilisation pratique de l'instruction if à des fins de "compilation conditionnelle", les règles réelles diffèrent.

À titre d'exemple, l'instruction suivante entraîne une erreur de compilation:

while (false) { x=3; }parce que l'instruction x=3;n'est pas accessible; mais le cas superficiellement similaire:

if (false) { x=3; }n'entraîne pas d'erreur de compilation. Un compilateur d'optimisation peut réaliser que l'instruction x=3;ne sera jamais exécutée et peut choisir d'omettre le code de cette instruction du fichier de classe généré, mais l'instructionx=3; n'est pas considérée comme "inaccessible" au sens technique spécifié ici.

La justification de ce traitement différent est de permettre aux programmeurs de définir des «variables de drapeau» telles que:

static final boolean DEBUG = false; puis écrivez du code tel que:

if (DEBUG) { x=3; } L'idée est qu'il devrait être possible de changer la valeur de DEBUG de false à true ou de true à false, puis de compiler le code correctement sans aucune autre modification du texte du programme.

Pourquoi l'instruction de rupture conditionnelle entraîne-t-elle une erreur du compilateur?

Comme indiqué dans les règles d'accessibilité de boucle, une boucle while peut également se terminer normalement si elle contient une instruction break accessible. Étant donné que les règles de joignabilité d'une ifde déclaration alors l' article ne prend pas l'état du ifcompte du tout, une telle condition ifdéclaration est alors clause est toujours considérée comme atteignable.

Si le breakest accessible, le code après la boucle est à nouveau considéré comme accessible. Puisqu'il n'y a pas de code accessible qui entraîne une interruption brutale après la boucle, la méthode est alors considérée comme capable de se terminer normalement, et donc le compilateur la signale comme une erreur.

Trevor Freeman
la source