Pourquoi la détection de code mort ne peut-elle pas être entièrement résolue par un compilateur?

192

Les compilateurs que j'utilisais en C ou Java ont une prévention de code mort (avertissement lorsqu'une ligne ne sera jamais exécutée). Mon professeur dit cependant que ce problème ne peut jamais être entièrement résolu par les compilateurs. Je me demandais pourquoi. Je ne suis pas trop familier avec le codage réel des compilateurs car il s'agit d'une classe basée sur la théorie. Mais je me demandais ce qu'ils vérifient (comme les chaînes d'entrée possibles par rapport aux entrées acceptables, etc.), et pourquoi cela est insuffisant.

Apprenant
la source
91
faire une boucle, mettre du code après, puis appliquer en.wikipedia.org/wiki/Halting_problem
zapl
48
if (isPrime(1234234234332232323423)){callSomething();}ce code appellera-t-il ou non quelque chose? Il existe de nombreux autres exemples, où décider si une fonction est appelée est de loin plus cher que de simplement l'inclure dans le programme.
idclev 463035818
33
public static void main(String[] args) {int counterexample = findCollatzConjectureCounterexample(); System.out.println(counterexample);}<- est le code mort d'appel println? Même les humains ne peuvent pas résoudre celui-là!
user253751
15
@ tobi303 n'est pas un excellent exemple, il est vraiment facile de factoriser les nombres premiers ... juste de ne pas les factoriser de manière relativement efficace. Le problème d'arrêt n'est pas dans NP, son insoluble.
en_Knight
57
@alephzero et en_Knight - Vous vous trompez tous les deux. isPrime est un excellent exemple. Vous avez fait l'hypothèse que la fonction vérifie un nombre premier. Peut-être que ce numéro était un numéro de série et qu'il effectue une recherche dans la base de données pour voir si l'utilisateur est membre d'Amazon Prime? La raison pour laquelle il s'agit d'un excellent exemple est que la seule façon de savoir si la condition est constante ou non est d'exécuter réellement la fonction isPrime. Alors maintenant, cela nécessiterait que le compilateur soit également un interprète. Mais cela ne résoudrait toujours pas les cas où les données sont volatiles.
Dunk

Réponses:

275

Le problème de code mort est lié au problème d'arrêt .

Alan Turing a prouvé qu'il est impossible d'écrire un algorithme général auquel sera attribué un programme et de pouvoir décider si ce programme s'arrête pour toutes les entrées. Vous pourrez peut-être écrire un tel algorithme pour des types de programmes spécifiques, mais pas pour tous les programmes.

Quel est le lien avec le code mort?

Le problème Halting est réductible au problème de la recherche de code mort. Autrement dit, si vous trouvez un algorithme qui peut détecter le code mort dans n'importe quel programme, vous pouvez utiliser cet algorithme pour tester si un programme va s'arrêter. Comme cela s'est avéré impossible, il s'ensuit que l'écriture d'un algorithme pour le code mort est également impossible.

Comment transférez-vous un algorithme pour le code mort dans un algorithme pour le problème d'arrêt?

Simple: vous ajoutez une ligne de code après la fin du programme dont vous souhaitez vérifier l'arrêt. Si votre détecteur de code mort détecte que cette ligne est morte, alors vous savez que le programme ne s'arrête pas. Si ce n'est pas le cas, vous savez que votre programme s'arrête (arrive à la dernière ligne, puis à votre ligne de code ajoutée).


Les compilateurs vérifient généralement les éléments qui peuvent être prouvés au moment de la compilation comme morts. Par exemple, des blocs qui dépendent de conditions qui peuvent être déterminées comme fausses au moment de la compilation. Ou toute instruction après un return(dans la même portée).

Ce sont des cas spécifiques, et il est donc possible d'écrire un algorithme pour eux. Il peut être possible d'écrire des algorithmes pour des cas plus compliqués (comme un algorithme qui vérifie si une condition est syntaxiquement une contradiction et par conséquent retournera toujours faux), mais cela ne couvrirait pas tous les cas possibles.

RealSkeptic
la source
8
Je dirais que le problème d'arrêt n'est pas applicable ici, car chaque plate-forme qui est une cible de compilation de chaque compilateur dans le monde réel a un maximum de données auxquelles elle peut accéder, elle aura donc un nombre maximal d'états, ce qui signifie qu'elle est en fait une machine à états finis, pas une machine de Turing. Le problème d'arrêt n'est pas insoluble pour les FSM, donc tout compilateur dans le monde réel peut effectuer une détection de code mort.
Vality
50
Les processeurs @Vality 64 bits peuvent adresser 2 ^ 64 octets. Amusez-vous à chercher dans les 256 ^ (2 ^ 64) états!
Daniel Wagner
82
@DanielWagner Cela ne devrait pas être un problème. La recherche d' 256^(2^64)états est O(1), donc la détection de code mort peut être effectuée en temps polynomial.
aebabis
13
@Leliel, c'était du sarcasme.
Paul Draper
44
@Vality: La plupart des ordinateurs modernes ont des disques, des périphériques d'entrée, des communications réseau, etc. Toute analyse complète devrait prendre en compte tous ces périphériques - y compris, littéralement, Internet et tout ce qui y est connecté. Ce n'est pas un problème traitable.
Nat
77

Eh bien, prenons la preuve classique de l'indécidabilité du problème d'arrêt et changeons le détecteur d'arrêt en détecteur de code mort!

Programme C #

using System;
using YourVendor.Compiler;

class Program
{
    static void Main(string[] args)
    {
        string quine_text = @"using System;
using YourVendor.Compiler;

class Program
{{
    static void Main(string[] args)
    {{
        string quine_text = @{0}{1}{0};
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {{
            System.Console.WriteLine({0}Dead code!{0});
        }}
    }}
}}";
        quine_text = string.Format(quine_text, (char)34, quine_text);

        if (YourVendor.Compiler.HasDeadCode(quine_text))
        {
            System.Console.WriteLine("Dead code!");
        }
    }
}

S'il YourVendor.Compiler.HasDeadCode(quine_text)revient false, alors la ligne System.Console.WriteLn("Dead code!");ne sera jamais exécutée, donc ce programme a en fait un code mort et le détecteur était erroné.

Mais s'il revient true, alors la ligne System.Console.WriteLn("Dead code!");sera exécutée, et comme il n'y a plus de code dans le programme, il n'y a plus de code mort, donc encore une fois, le détecteur s'est trompé.

Donc voilà, un détecteur de code mort qui ne renvoie que "Il y a du code mort" ou "Il n'y a pas de code mort" doit parfois donner de mauvaises réponses.

Joker_vD
la source
1
Si j'ai bien compris votre argument, alors techniquement une autre option serait qu'il n'est pas possible d'écrire un détecteur qui est un code mort, mais il est possible d'écrire un détecteur de code mort dans le cas général. :-)
abligh
1
incrément pour la réponse godélienne.
Jared Smith
@abligh Ugh, c'était un mauvais choix de mots. Je ne me nourris pas réellement du code source du détecteur de code mort, mais du code source du programme qui l'utilise. Certes, à un moment donné, il devrait probablement examiner son propre code, mais c'est son affaire.
Joker_vD
65

Si le problème d'arrêt est trop obscur, pensez-y de cette façon.

Prenons un problème mathématique qui est censé être vrai pour tous les n entiers positifs , mais qui n'a pas été prouvé pour tous les n . Un bon exemple serait la conjecture de Goldbach, selon laquelle tout entier pair positif supérieur à deux peut être représenté par la somme de deux nombres premiers. Ensuite (avec une bibliothèque bigint appropriée) exécutez ce programme (le pseudocode suit):

 for (BigInt n = 4; ; n+=2) {
     if (!isGoldbachsConjectureTrueFor(n)) {
         print("Conjecture is false for at least one value of n\n");
         exit(0);
     }
 }

L'implémentation de isGoldbachsConjectureTrueFor()est laissée comme un exercice pour le lecteur mais à cette fin pourrait être une simple itération sur tous les nombres premiers inférieurs àn

Maintenant, logiquement, ce qui précède doit être soit l'équivalent de:

 for (; ;) {
 }

(c'est-à-dire une boucle infinie) ou

print("Conjecture is false for at least one value of n\n");

car la conjecture de Goldbach doit être vraie ou fausse. Si un compilateur pouvait toujours éliminer le code mort, il y aurait certainement du code mort à éliminer ici dans les deux cas. Cependant, ce faisant, votre compilateur devra au moins résoudre des problèmes arbitrairement difficiles. Nous pourrions fournir des problèmes prouvablement dur qu'il faudrait résoudre (par exemple les problèmes NP-complets) pour déterminer quel bit de code à éliminer. Par exemple, si nous prenons ce programme:

 String target = "f3c5ac5a63d50099f3b5147cabbbd81e89211513a92e3dcd2565d8c7d302ba9c";
 for (BigInt n = 0; n < 2**2048; n++) {
     String s = n.toString();
     if (sha256(s).equals(target)) {
         print("Found SHA value\n");
         exit(0);
     }
 }
 print("Not found SHA value\n");

nous savons que le programme imprimera soit "Valeur SHA trouvée" soit "Valeur SHA non trouvée" (points bonus si vous pouvez me dire laquelle est vraie). Cependant, pour qu'un compilateur puisse raisonnablement optimiser cela prendrait de l'ordre de 2 ^ 2048 itérations. Ce serait en fait une excellente optimisation car je prédis que le programme ci-dessus fonctionnerait (ou pourrait) fonctionner jusqu'à la mort thermique de l'univers plutôt que d'imprimer quoi que ce soit sans optimisation.

abligh
la source
4
C'est de loin la meilleure réponse +1
jean
2
Ce qui rend les choses particulièrement intéressantes, c'est l'ambiguïté sur ce que la norme C autorise ou n'autorise pas lorsqu'il s'agit de supposer que les boucles se termineront. Il est utile de permettre à un compilateur de reporter des calculs lents dont les résultats peuvent ou non être utilisés jusqu'au point où leurs résultats seraient réellement nécessaires; cette optimisation pourrait dans certains cas être utile même si le compilateur ne peut pas prouver que les calculs se terminent.
supercat
2
2 ^ 2048 itérations? Même la pensée profonde abandonnerait.
Peter Mortensen
Il affichera "Valeur SHA trouvée" avec une probabilité très élevée, même si cette cible était une chaîne aléatoire de 64 chiffres hexadécimaux. À moins de sha256retourner un tableau d'octets et les tableaux d'octets ne sont pas comparables aux chaînes de votre langue.
user253751
4
Implementation of isGoldbachsConjectureTrueFor() is left as an exercise for the readerCela m'a fait rire.
biziclop
34

Je ne sais pas si C ++ ou Java ont une Evalfonction de type, mais de nombreux langages vous permettent d'appeler des méthodes par leur nom . Considérez l'exemple VBA (artificiel) suivant.

Dim methodName As String

If foo Then
    methodName = "Bar"
Else
    methodName = "Qux"
End If

Application.Run(methodName)

Le nom de la méthode à appeler est impossible à connaître jusqu'à l'exécution. Par conséquent, par définition, le compilateur ne peut pas savoir avec une certitude absolue qu'une méthode particulière n'est jamais appelée.

En fait, étant donné l'exemple de l'appel d'une méthode par son nom, la logique de branchement n'est même pas nécessaire. Dire simplement

Application.Run("Bar")

C'est plus que ce que le compilateur peut déterminer. Lorsque le code est compilé, tout ce que le compilateur sait, c'est qu'une certaine valeur de chaîne est transmise à cette méthode. Il ne vérifie pas si cette méthode existe jusqu'à l'exécution. Si la méthode n'est pas appelée ailleurs, via des méthodes plus normales, une tentative de recherche de méthodes mortes peut renvoyer des faux positifs. Le même problème existe dans n'importe quel langage qui permet d'appeler du code via la réflexion.

Canard en caoutchouc
la source
2
En Java (ou C #), cela pourrait se faire avec réflexion. C ++, vous pourriez probablement retirer un peu de méchanceté en utilisant des macros pour le faire. Ce ne serait pas joli, mais C ++ l'est rarement.
Darrel Hoffman
6
@DarrelHoffman - Les macros sont développées avant que le code ne soit donné au compilateur, donc les macros ne sont certainement pas comme vous le feriez. Les pointeurs vers les fonctions vous indiquent comment procéder. Je n'ai pas utilisé C ++ depuis des années, alors excusez-moi si mes noms de type exacts sont incorrects, mais vous pouvez simplement stocker une carte de chaînes pour les pointeurs de fonction. Ensuite, ayez quelque chose qui accepte une chaîne de l'entrée utilisateur, recherche cette chaîne dans la carte, puis exécute la fonction pointée.
ArtOfWarfare du
1
@ArtOfWarfare, nous ne parlons pas de la façon dont cela pourrait être fait. De toute évidence, une analyse sémantique du code peut être effectuée pour trouver cette situation, le fait était que le compilateur ne le fait pas . C'est possible, peut-être, mais ce n'est pas le cas.
RubberDuck
3
@ArtOfWarfare: Si vous voulez taper, bien sûr. Je considère que le préprocesseur fait partie du compilateur, même si je sais que ce n'est pas le cas techniquement. Quoi qu'il en soit, les pointeurs de fonction peuvent enfreindre la règle selon laquelle les fonctions ne sont référencées directement nulle part - elles le sont, tout comme un pointeur au lieu d'un appel direct, un peu comme un délégué en C #. Le C ++ est en général beaucoup plus difficile à prédire pour un compilateur car il a tellement de façons de faire les choses indirectement. Même les tâches aussi simples que «trouver toutes les références» ne sont pas anodines, car elles peuvent se cacher dans les typedefs, les macros, etc. Pas étonnant qu'il ne puisse pas trouver facilement le code mort.
Darrel Hoffman
1
Vous n'avez même pas besoin d'appels de méthode dynamique pour faire face à ce problème. Toute méthode publique peut être appelée par une fonction non encore écrite qui dépendra de la classe déjà compilée en Java ou C # ou tout autre langage compilé avec un mécanisme de liaison dynamique. Si les compilateurs les éliminaient comme «code mort», nous ne pourrions pas empaqueter des bibliothèques précompilées pour la distribution (NuGet, jars, roues Python avec composant binaire).
jpmc26
12

Le code mort inconditionnel peut être détecté et supprimé par des compilateurs avancés.

Mais il existe également un code mort conditionnel. Il s'agit d'un code qui ne peut pas être connu au moment de la compilation et ne peut être détecté que pendant l'exécution. Par exemple, un logiciel peut être configurable pour inclure ou exclure certaines fonctionnalités en fonction des préférences de l'utilisateur, ce qui rend certaines sections de code apparemment mortes dans des scénarios particuliers. Ce n'est pas un vrai code mort.

Il existe des outils spécifiques qui peuvent effectuer des tests, résoudre les dépendances, supprimer le code mort conditionnel et recombiner le code utile lors de l'exécution pour plus d'efficacité. C'est ce qu'on appelle l'élimination dynamique du code mort. Mais comme vous pouvez le voir, cela dépasse le cadre des compilateurs.

dspfnder
la source
5
"Le code mort inconditionnel peut être détecté et supprimé par des compilateurs avancés." Cela ne semble pas probable. La mort du code peut dépendre du résultat d'une fonction donnée, et cette fonction donnée peut résoudre des problèmes arbitraires. Votre déclaration affirme donc que les compilateurs avancés peuvent résoudre des problèmes arbitraires.
Taemyr
6
@Taemyr Alors on ne sait pas qu'il est mort inconditionnellement, maintenant?
JAB
1
@Taemyr Vous semblez mal comprendre le mot «inconditionnel». Si la mort du code dépend du résultat d'une fonction, alors c'est du code mort conditionnel. La "condition" étant le résultat de la fonction. Pour être « inconditionnel » il aurait à ne pas dépendre d'un résultat.
Kyeotic
12

Un exemple simple:

int readValueFromPort(const unsigned int portNum);

int x = readValueFromPort(0x100); // just an example, nothing meaningful
if (x < 2)
{
    std::cout << "Hey! X < 2" << std::endl;
}
else
{
    std::cout << "X is too big!" << std::endl;
}

Supposons maintenant que le port 0x100 est conçu pour renvoyer uniquement 0 ou 1. Dans ce cas, le compilateur ne peut pas comprendre que le elsebloc ne sera jamais exécuté.

Cependant, dans cet exemple de base:

bool boolVal = /*anything boolean*/;

if (boolVal)
{
  // Do A
}
else if (!boolVal)
{
  // Do B
}
else
{
  // Do C
}

Ici, le compilateur peut calculer si le elsebloc est un code mort. Ainsi, le compilateur peut avertir du code mort uniquement s'il dispose de suffisamment de données pour comprendre le code mort et il doit également savoir comment appliquer ces données afin de déterminer si le bloc donné est un code mort.

ÉDITER

Parfois, les données ne sont tout simplement pas disponibles au moment de la compilation:

// File a.cpp
bool boolMethod();

bool boolVal = boolMethod();

if (boolVal)
{
  // Do A
}
else
{
  // Do B
}

//............
// File b.cpp
bool boolMethod()
{
    return true;
}

Lors de la compilation de a.cpp, le compilateur ne peut pas savoir que cela boolMethodrevient toujours true.

Alex Lop.
la source
1
Bien qu'il soit strictement vrai que le compilateur ne sait pas, je pense que c'est dans l'esprit de la question de se demander également si l' éditeur de liens peut savoir.
Casey Kuball du
1
@Darthfett Ce n'est pas la responsabilité de l' éditeur de liens . L'éditeur de liens n'analyse pas le contenu du code compilé. L'éditeur de liens (en général) relie simplement les méthodes et les données globales, il ne se soucie pas du contenu. Cependant, certains compilateurs ont la possibilité de concaténer les fichiers source (comme ICC), puis d'effectuer l'optimisation. Dans ce cas, le cas sous EDIT est couvert, mais cette option affectera le temps de compilation, surtout lorsque le projet est volumineux.
Alex Lop.
Cette réponse me semble trompeuse; vous donnez deux exemples où ce n'est pas possible parce que toutes les informations ne sont pas disponibles, mais ne devriez-vous pas dire que c'est impossible même si les informations sont là?
Anton Golov
@AntonGolovIt n'est pas toujours vrai. Dans de nombreux cas, lorsque les informations sont là, les compilateurs peuvent détecter le code mort et l'optimiser.
Alex Lop.
@abforce juste un bloc de code. Cela aurait pu être autre chose. :)
Alex Lop.
4

Le compilateur manquera toujours d'informations sur le contexte. Par exemple, vous savez peut-être qu'une valeur double n'excède jamais 2, car c'est une caractéristique de la fonction mathématique que vous utilisez à partir d'une bibliothèque. Le compilateur ne voit même pas le code dans la bibliothèque, et il ne peut jamais connaître toutes les fonctionnalités de toutes les fonctions mathématiques et détecter toutes les manières compliquées et compliquées de les implémenter.

cdonat
la source
4

Le compilateur ne voit pas nécessairement tout le programme. Je pourrais avoir un programme qui appelle une bibliothèque partagée, qui rappelle une fonction de mon programme qui n'est pas appelée directement.

Ainsi, une fonction qui est morte par rapport à la bibliothèque avec laquelle elle est compilée pourrait devenir active si cette bibliothèque était modifiée au moment de l'exécution.

Steve Sanbeg
la source
3

Si un compilateur pouvait éliminer avec précision tout le code mort, il serait appelé interprète .

Considérez ce scénario simple:

if (my_func()) {
  am_i_dead();
}

my_func() peut contenir du code arbitraire et pour que le compilateur détermine s'il renvoie vrai ou faux, il devra soit exécuter le code, soit faire quelque chose qui est fonctionnellement équivalent à l'exécution du code.

L'idée d'un compilateur est qu'il n'effectue qu'une analyse partielle du code, simplifiant ainsi le travail d'un environnement d'exécution distinct. Si vous effectuez une analyse complète, ce n'est plus un compilateur.


Si vous considérez le compilateur comme une fonction c(), où c(source)=compiled code, et l'environnement d'exécution comme r(), où r(compiled code)=program output, alors pour déterminer la sortie de tout code source, vous devez calculer la valeur der(c(source code)) . Si calcul c()nécessite la connaissance de la valeur r(c())pour toute entrée, il n'y a pas besoin d'un séparé r()et c()vous pouvez simplement tirer une fonction i()de c()telle sorte que i(source)=program output.

biziclop
la source
2

D'autres ont commenté le problème de l'arrêt, etc. Celles-ci s'appliquent généralement à des portions de fonctions. Cependant, il peut être difficile / impossible de savoir si même un type entier (classe / etc) est utilisé ou non.

Dans .NET / Java / JavaScript et d'autres environnements pilotés par le runtime, il n'y a rien qui empêche le chargement des types via la réflexion. Ceci est populaire avec les frameworks d'injection de dépendance, et est encore plus difficile à raisonner face à la désérialisation ou au chargement de module dynamique.

Le compilateur ne peut pas savoir si de tels types seraient chargés. Leurs noms pourraient provenir de fichiers de configuration externes lors de l'exécution.

Vous voudrez peut-être chercher autour de l' arbre qui est un terme courant pour les outils qui tentent de supprimer en toute sécurité les sous-graphiques de code inutilisés.

Drew Noakes
la source
Je ne connais pas Java et javascript, mais .NET a en fait un plugin resharper pour ce type de détection DI (appelé Agent Mulder). Bien sûr, il ne pourra pas détecter les fichiers de configuration, mais il est capable de détecter les confits dans le code (ce qui est beaucoup plus populaire).
Fait
2

Prenez une fonction

void DoSomeAction(int actnumber) 
{
    switch(actnumber) 
    {
        case 1: Action1(); break;
        case 2: Action2(); break;
        case 3: Action3(); break;
    }
}

Pouvez-vous prouver que actnumbercela ne sera jamais 2ainsi que ce Action2()n'est jamais appelé ...?

CiaPan
la source
7
Si vous pouvez analyser les appelants de la fonction, vous pourrez peut-être le faire, oui.
abligh
2
@abligh Mais le compilateur ne peut généralement pas analyser tout le code appelant. Quoi qu'il en soit, même si cela était possible, l'analyse complète pourrait ne nécessiter qu'une simulation de tous les flux de contrôle possibles, ce qui est presque toujours impossible en raison des ressources et du temps nécessaires. Ainsi, même s'il existe théoriquement une preuve que « Action2()ne sera jamais appelé», il est impossible de prouver la revendication dans la pratique - ne peut pas être entièrement résolu par un compilateur . La différence est comme «il existe un nombre X» vs «nous pouvons écrire le nombre X en décimal». Pour certains X, ce dernier ne se produira jamais bien que le premier soit vrai.
CiaPan
C'est une mauvaise réponse. les autres réponses prouvent qu'il est impossible de savoir si actnumber==2. Cette réponse prétend simplement que c'est difficile sans même mentionner une complexité.
MSalters du
1

Je ne suis pas d'accord sur le problème de l'arrêt. Je n'appellerais pas un tel code mort même si en réalité il ne sera jamais atteint.

Considérons plutôt:

for (int N = 3;;N++)
  for (int A = 2; A < int.MaxValue; A++)
    for (int B = 2; B < int.MaxValue; B++)
    {
      int Square = Math.Pow(A, N) + Math.Pow(B, N);
      float Test = Math.Sqrt(Square);
      if (Test == Math.Trunc(Test))
        FermatWasWrong();
    }

private void FermatWasWrong()
{
  Press.Announce("Fermat was wrong!");
  Nobel.Claim();
}

(Ignorer les erreurs de type et de débordement) Code mort?

Loren Pechtel
la source
2
Le dernier théorème de Fermat a été prouvé en 1994. Ainsi, une implémentation correcte de votre méthode ne pourrait jamais exécuter FermatWasWrong. Je soupçonne que votre implémentation exécutera FermatWasWrong, car vous pouvez atteindre la limite de précision des flottants.
Taemyr
@Taemyr Aha! Ce programme ne teste pas correctement le dernier théorème de Fermat; un contre-exemple pour ce qu'il teste est N = 3, A = 65536, B = 65536 (qui donne Test = 0)
user253751
@immibis Oui, j'ai manqué qu'il débordera avant que la précision des flotteurs ne devienne un problème.
Taemyr
@immibis Notez le bas de mon message: Ignorez les erreurs de type et de débordement. Je prenais simplement ce que je pensais être un problème non résolu comme base d'une décision - je sais que le code n'est pas parfait. C'est un problème qui ne peut pas être forcé de toute façon.
Loren Pechtel
-1

Regardez cet exemple:

public boolean isEven(int i){

    if(i % 2 == 0)
        return true;
    if(i % 2 == 1)
        return false;
    return false;
}

Le compilateur ne peut pas savoir qu'un int ne peut être que pair ou impair. Par conséquent, le compilateur doit être capable de comprendre la sémantique de votre code. Comment cela devrait-il être mis en œuvre? Le compilateur ne peut pas garantir que le retour le plus bas ne sera jamais exécuté. Par conséquent, le compilateur ne peut pas détecter le code mort.

utilisateur
la source
1
Umm, vraiment? Si j'écris cela en C # + ReSharper, j'obtiens quelques indices. Les suivre me donne enfin le code return i%2==0;.
Thomas Weller
10
Votre exemple est trop simple pour être convaincant. Le cas spécifique de i % 2 == 0et i % 2 != 0ne nécessite même pas de raisonnement sur la valeur d'un module entier une constante (ce qui est toujours facile à faire), il ne nécessite que l'élimination de la sous-expression commune et le principe général (canonisation, même) qui if (cond) foo; if (!cond) bar;peut être simplifié if (cond) foo; else bar;. Bien sûr, "comprendre la sémantique" est un problème très difficile, mais ce post ne montre pas qu'il l'est, ni ne montre que la résolution de ce problème difficile est nécessaire pour la détection de code mort.
5
Dans votre exemple, un compilateur d'optimisation repérera la sous i % 2- expression commune et la retirera dans une variable temporaire. Il reconnaîtra alors que les deux ifinstructions s'excluent mutuellement et peuvent être écrites comme if(a==0)...else..., puis remarquera que tous les chemins d'exécution possibles passent par les deux premières returninstructions et donc la troisième returninstruction est du code mort. (Un bon compilateur d'optimisation est encore plus agressif: GCC a transformé mon code de test en une paire d'opérations de manipulation de bits).
Mark
1
Cet exemple est bon pour moi. Cela représente le cas où un compilateur ne connaît pas certaines circonstances factuelles. Il en va de même if (availableMemory()<0) then {dead code}.
Little Santi
1
@LittleSanti: En fait, GCC détectera que tout ce que vous avez écrit est du code mort! Ce n'est pas seulement la {dead code}partie. GCC le découvre en prouvant qu'il y a un débordement d'entier signé inévitable. Tout le code sur cet arc dans le graphe d'exécution est donc du code mort. GCC peut même supprimer la branche conditionnelle qui mène à cet arc.
MSalters