Perché un compilatore non può risolvere completamente il rilevamento di codici non funzionanti?


192

I compilatori che ho usato in C o Java hanno la prevenzione del codice morto (avviso quando una riga non verrà mai eseguita). Il mio professore afferma che questo problema non può mai essere completamente risolto dai compilatori. Mi chiedevo perché fosse così. Non ho troppa familiarità con l'effettiva codifica dei compilatori poiché si tratta di una classe basata sulla teoria. Ma mi chiedevo cosa controllassero (come possibili stringhe di input vs input accettabili, ecc.) E perché ciò non fosse sufficiente.


91
crea un ciclo, inserisci il codice dopo di esso, quindi applica en.wikipedia.org/wiki/Halting_problem
zapl

48
if (isPrime(1234234234332232323423)){callSomething();}questo codice chiamerà mai qualcosa o no? Ci sono molti altri esempi, in cui decidere se una funzione viene mai chiamata è di gran lunga più costoso che includerla nel programma.
idclev 463035818,

33
public static void main(String[] args) {int counterexample = findCollatzConjectureCounterexample(); System.out.println(counterexample);}<- è il codice morto di println call? Nemmeno gli umani possono risolverlo!
user253751

15
@ tobi303 non è un grande esempio, è davvero facile fattorizzare i numeri primi ... solo non fattorizzarli in modo relativamente efficiente. Il problema di arresto non è in NP, è irrisolvibile.
en_Knight

57
@alephzero e en_Knight - Sbagli entrambi. isPrime è un ottimo esempio. Hai ipotizzato che la funzione stia verificando un numero primo. Forse quel numero era un numero seriale e fa una ricerca nel database per vedere se l'utente è un membro di Amazon Prime? Il motivo per cui è un ottimo esempio è perché l'unico modo per sapere se la condizione è costante o no è eseguire effettivamente la funzione isPrime. Quindi ora ciò richiederebbe che il compilatore sia anche un interprete. Ma ciò non risolverebbe comunque quei casi in cui i dati sono volatili.
Dunk,

Risposte:


275

Il problema del codice morto è correlato al problema di Halting .

Alan Turing ha dimostrato che è impossibile scrivere un algoritmo generale a cui verrà assegnato un programma ed essere in grado di decidere se quel programma si interrompe per tutti gli input. Potresti essere in grado di scrivere un tale algoritmo per tipi specifici di programmi, ma non per tutti i programmi.

Come si collega questo al codice morto?

Il problema di Halting è riducibile al problema di trovare un codice morto. Cioè, se trovi un algoritmo in grado di rilevare il codice morto in qualsiasi programma, puoi utilizzare tale algoritmo per verificare se un programma si arresterà. Dal momento che è stato dimostrato che è impossibile, ne consegue che è impossibile anche scrivere un algoritmo per codice morto.

Come si trasferisce un algoritmo per codice morto in un algoritmo per il problema di Halting?

Semplice: aggiungi una riga di codice dopo la fine del programma che desideri verificare. Se il tuo rilevatore di codice morto rileva che questa linea è morta, allora sai che il programma non si ferma. In caso contrario, sai che il tuo programma si arresta (arriva all'ultima riga e quindi alla riga di codice aggiunta).


I compilatori di solito verificano la morte di elementi che possono essere dimostrati in fase di compilazione. Ad esempio, blocchi che dipendono da condizioni che possono essere determinate come false al momento della compilazione. O qualsiasi affermazione dopo un return(all'interno dello stesso ambito).

Questi sono casi specifici e quindi è possibile scrivere un algoritmo per loro. Potrebbe essere possibile scrivere algoritmi per casi più complicati (come un algoritmo che controlla se una condizione è sintatticamente una contraddizione e quindi restituirà sempre falso), ma che comunque non coprirebbe tutti i casi possibili.


8
Direi che il problema dell'arresto non è applicabile qui, poiché ogni piattaforma che è un obiettivo di compilazione di ogni compilatore nel mondo reale ha una quantità massima di dati a cui può accedere, quindi avrà un numero massimo di stati, il che significa che è in effetti una macchina a stati finiti, non una macchina di turing. Il problema di arresto non è irrisolvibile per gli FSM, quindi qualsiasi compilatore nel mondo reale può eseguire il rilevamento del codice morto.
Valità,

50
I processori a 64 bit di @Vality possono indirizzare 2 ^ 64 byte. Divertiti a cercare tutti i 256 ^ (2 ^ 64) stati!
Daniel Wagner,

82
@DanielWagner Questo non dovrebbe essere un problema. La ricerca degli 256^(2^64)stati è O(1), quindi il rilevamento del codice morto può essere effettuato in tempo polinomiale.
aebabis,

13
@Leliel, quello era il sarcasmo.
Paul Draper,

44
@Valità: la maggior parte dei computer moderni ha dischi, dispositivi di input, comunicazioni di rete, ecc. Qualsiasi analisi completa dovrebbe prendere in considerazione tutti questi dispositivi - incluso, letteralmente, Internet e tutto ciò che è collegato. Questo non è un problema trattabile.
Nat

77

Bene, prendiamo la classica prova dell'indecidibilità del problema di arresto e cambiamo il rilevatore di arresto in un rilevatore di codice morto!

Programma 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!");
        }
    }
}

Se YourVendor.Compiler.HasDeadCode(quine_text)ritorna false, poi la linea System.Console.WriteLn("Dead code!");non sarà mai eseguita, quindi questo programma in realtà non hanno codice morto, e il rilevatore era sbagliato.

Ma se ritorna true, allora la riga System.Console.WriteLn("Dead code!");verrà eseguita e poiché non c'è più codice nel programma, non c'è affatto un codice morto, quindi il rivelatore era sbagliato.

Quindi il gioco è fatto, un rilevatore di codice morto che restituisce solo "C'è codice morto" o "Non c'è codice morto" a volte deve dare risposte sbagliate.


1
Se ho capito correttamente il tuo argomento, tecnicamente un'altra opzione sarebbe che non è possibile scrivere un tutto che è un rilevatore di codice morto, ma è possibile scrivere un rilevatore di codice morto nel caso generale. :-)
circa

1
incremento per la risposta godeliana.
Jared Smith,

@abligh Ugh, è stata una pessima scelta di parole. In realtà non sto fornendo il codice sorgente del rivelatore di codice morto a se stesso, ma il codice sorgente del programma che lo utilizza. Sicuramente, ad un certo punto probabilmente dovrebbe guardare al proprio codice, ma è affar suo.
Joker_vD,

65

Se il problema di arresto è troppo oscuro, pensaci in questo modo.

Prendi un problema matematico che si ritiene sia vero per tutti gli interi positivi n , ma non è stato dimostrato che sia vero per ogni n . Un buon esempio sarebbe la congettura di Goldbach , secondo cui qualsiasi numero intero pari positivo maggiore di due può essere rappresentato dalla somma di due numeri primi. Quindi (con una libreria bigint appropriata) esegui questo programma (segue lo pseudocodice):

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

L'implementazione di isGoldbachsConjectureTrueFor()è lasciata come esercizio per il lettore, ma a questo scopo potrebbe essere una semplice iterazione su tutti i numeri primi di menon

Ora, logicamente, quanto sopra deve essere equivalente a:

 for (; ;) {
 }

(ovvero un ciclo infinito) o

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

poiché la congettura di Goldbach deve essere vera o no. Se un compilatore potesse sempre eliminare il codice morto, ci sarebbe sicuramente un codice morto da eliminare qui in entrambi i casi. Tuttavia, almeno così facendo il compilatore dovrebbe risolvere problemi arbitrariamente difficili. Potremmo fornire problemi dimostrabilmente duro che avrebbe dovuto risolvere i problemi (es NP-completi) per determinare quale porzione di codice per eliminare. Ad esempio se prendiamo questo programma:

 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");

sappiamo che il programma stamperà "Valore SHA trovato" o "Valore SHA non trovato" (punti bonus se puoi dirmi quale è vero). Tuttavia, affinché un compilatore sia in grado di ottimizzare ragionevolmente ciò richiederebbe l'ordine di 2 ^ 2048 iterazioni. Sarebbe in effetti una grande ottimizzazione poiché prevedo che il programma di cui sopra sarebbe (o potrebbe) funzionare fino alla morte per calore dell'universo piuttosto che stampare qualsiasi cosa senza ottimizzazione.


4
È la risposta migliore di gran lunga +1
gennaio

2
Ciò che rende le cose particolarmente interessanti è l'ambiguità su ciò che lo standard C consente o non consente quando si tratta di supporre che i loop terminino. È utile consentire a un compilatore di differire i calcoli lenti i cui risultati possono o meno essere utilizzati fino al punto in cui i loro risultati sarebbero effettivamente necessari; questa ottimizzazione potrebbe in alcuni casi essere utile anche se il compilatore non è in grado di provare la conclusione dei calcoli.
supercat,

2
2 ^ 2048 iterazioni? Anche Deep Thought si arrenderebbe.
Peter Mortensen,

Stampa "Valore SHA trovato" con probabilità molto alta, anche se quel bersaglio era una stringa casuale di 64 cifre esadecimali. A meno che non sha256ritorni un array di byte e gli array di byte non si equivalgono a stringhe nella tua lingua.
user253751

4
Implementation of isGoldbachsConjectureTrueFor() is left as an exercise for the readerQuesto mi ha fatto ridere.
biziclop,

34

Non so se C ++ o Java abbiano una Evalfunzione di tipo, ma molte lingue ti permettono di chiamare i metodi per nome . Considera l'esempio VBA seguente (inventato).

Dim methodName As String

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

Application.Run(methodName)

Il nome del metodo da chiamare è impossibile da conoscere fino al runtime. Pertanto, per definizione, il compilatore non può sapere con assoluta certezza che un particolare metodo non viene mai chiamato.

In realtà, dato l'esempio di chiamare un metodo per nome, la logica di ramificazione non è nemmeno necessaria. Sto semplicemente dicendo

Application.Run("Bar")

È più di quanto il compilatore possa determinare. Quando il codice viene compilato, tutto il compilatore sa che un determinato valore di stringa viene passato a quel metodo. Non controlla se quel metodo esiste fino al runtime. Se il metodo non viene chiamato altrove, attraverso metodi più normali, un tentativo di trovare metodi morti può restituire falsi positivi. Lo stesso problema esiste in qualsiasi lingua che consente di chiamare il codice tramite reflection.


2
In Java (o C #), questo potrebbe essere fatto con la riflessione. C ++ probabilmente potresti ottenere un po 'di cattiveria usando le macro per farlo. Non sarebbe carino, ma C ++ lo è raramente.
Darrel Hoffman,

6
@DarrelHoffman - Le macro vengono espanse prima che il codice venga fornito al compilatore, quindi le macro sicuramente non sono come lo faresti. Puntatori alle funzioni è come lo faresti. Non uso C ++ da anni, quindi mi scusi se i nomi dei miei esatti tipi sono errati, ma puoi semplicemente memorizzare una mappa di stringhe per far funzionare i puntatori. Quindi avere qualcosa che accetta una stringa dall'input dell'utente, cerca quella stringa nella mappa e quindi esegue la funzione puntata.
ArtOfWarfare il

1
@ArtOfWarfare non stiamo parlando di come si possa fare. Ovviamente, l'analisi semantica del codice può essere fatta per trovare questa situazione, il punto era che il compilatore no . Potrebbe, forse, forse, ma non lo è.
RubberDuck,

3
@ArtOfWarfare: se vuoi nitpick, certo. Considero il preprocessore parte del compilatore, anche se so che tecnicamente non lo è. Ad ogni modo, i puntatori a funzione potrebbero infrangere la regola secondo cui le funzioni non sono direttamente referenziate da nessuna parte - sono, proprio come un puntatore anziché una chiamata diretta, proprio come un delegato in C #. Il C ++ è in genere molto più difficile da prevedere per un compilatore poiché ha così tanti modi di fare le cose indirettamente. Anche le attività semplici come "trova tutti i riferimenti" non sono banali, in quanto possono nascondersi in typedef, macro, ecc. Non sorprende che non riesca a trovare facilmente il codice morto.
Darrel Hoffman,

1
Non hai nemmeno bisogno di chiamate a metodi dinamici per affrontare questo problema. Qualsiasi metodo pubblico può essere chiamato da una funzione non ancora scritta che dipenderà dalla classe già compilata in Java o C # o da qualsiasi altro linguaggio compilato con un meccanismo per il collegamento dinamico. Se i compilatori li eliminassero come "codice morto", allora non saremmo in grado di impacchettare librerie precompilate per la distribuzione (NuGet, jars, ruote Python con componente binario).
jpmc26,

12

Il codice morto incondizionato può essere rilevato e rimosso da compilatori avanzati.

Ma c'è anche un codice morto condizionale. Questo è un codice che non può essere conosciuto al momento della compilazione e può essere rilevato solo durante il runtime. Ad esempio, un software può essere configurabile per includere o escludere determinate funzionalità in base alle preferenze dell'utente, rendendo alcune sezioni di codice apparentemente morte in scenari particolari. Questo non è un vero codice morto.

Esistono strumenti specifici che possono eseguire test, risolvere dipendenze, rimuovere il codice morto condizionale e ricombinare il codice utile in fase di esecuzione per l'efficienza. Questo si chiama eliminazione dinamica del codice morto. Ma come puoi vedere, va oltre lo scopo dei compilatori.


5
"Il codice morto incondizionato può essere rilevato e rimosso da compilatori avanzati." Questo non sembra probabile. La deadness del codice può dipendere dall'esito di una determinata funzione e tale funzione può risolvere problemi arbitrari. Quindi la tua affermazione afferma che i compilatori avanzati possono risolvere problemi arbitrari.
Taemyr,

6
@Taemyr Allora non sarebbe noto per essere incondizionatamente morto, ora lo farebbe?
JAB,

1
@Taemyr Sembra che tu abbia frainteso la parola "incondizionato". Se il deadness del codice dipende dal risultato di una funzione, allora è un dead code condizionale. La "condizione" è il risultato della funzione. Essere "senza condizioni" avrebbe dovuto non dipendere da alcun risultato.
Kyeotic,

12

Un semplice esempio:

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;
}

Ora supponiamo che la porta 0x100 sia progettata per restituire solo 0 o 1. In quel caso il compilatore non può capire che il elseblocco non verrà mai eseguito.

Tuttavia, in questo esempio di base:

bool boolVal = /*anything boolean*/;

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

Qui il compilatore può calcolare che il elseblocco è un codice morto. Quindi il compilatore può avvertire del codice morto solo se ha abbastanza dati per capire il codice morto e dovrebbe anche sapere come applicare quei dati per capire se il blocco dato è un codice morto.

MODIFICARE

A volte i dati non sono disponibili al momento della compilazione:

// File a.cpp
bool boolMethod();

bool boolVal = boolMethod();

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

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

Durante la compilazione di a.cpp il compilatore non può sapere che boolMethodritorna sempre true.


1
Sebbene sia vero che il compilatore non lo sappia, penso che sia nello spirito della domanda anche chiedere al linker di saperlo.
Casey Kuball,

1
@Darthfett Non è responsabilità del linker . Linker non analizza il contenuto del codice compilato. Il linker (in generale) collega solo i metodi e i dati globali, non si preoccupa del contenuto. Tuttavia alcuni compilatori hanno la possibilità di concatenare i file di origine (come ICC) e quindi eseguire l'ottimizzazione. In tal caso, il caso in EDIT è coperto, ma questa opzione influirà sul tempo di compilazione, specialmente quando il progetto è grande.
Alex Lop.

Questa risposta mi sembra fuorviante; stai dando due esempi in cui non è possibile perché non tutte le informazioni sono disponibili, ma non dovresti dire che è impossibile anche se le informazioni sono lì?
Anton Golov,

@AntonGolovNon è sempre vero. In molti casi quando le informazioni sono lì, i compilatori possono rilevare il codice morto e ottimizzarlo.
Alex Lop.

@abforce solo un blocco di codice. Sarebbe potuto essere qualcos'altro. :)
Alex Lop.

4

Il compilatore mancherà sempre di alcune informazioni di contesto. Ad esempio, potresti sapere che un doppio valore non supera mai 2, poiché questa è una caratteristica della funzione matematica, che usi da una libreria. Il compilatore non vede nemmeno il codice nella libreria e non può mai conoscere tutte le caratteristiche di tutte le funzioni matematiche e rilevare tutti i modi complessi e complicati per implementarli.


4

Il compilatore non vede necessariamente l'intero programma. Potrei avere un programma che chiama una libreria condivisa, che richiama una funzione nel mio programma che non viene chiamata direttamente.

Quindi una funzione che è morta rispetto alla libreria contro cui è stata compilata potrebbe diventare viva se quella libreria fosse cambiata in fase di esecuzione.


3

Se un compilatore potesse eliminare accuratamente tutti i codici morti, verrebbe chiamato un interprete .

Considera questo semplice scenario:

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

my_func() può contenere codice arbitrario e affinché il compilatore possa determinare se restituisce vero o falso, dovrà eseguire il codice o fare qualcosa che sia funzionalmente equivalente all'esecuzione del codice.

L'idea di un compilatore è che esegue solo un'analisi parziale del codice, semplificando così il lavoro di un ambiente di esecuzione separato. Se esegui un'analisi completa, questo non è più un compilatore.


Se si considera il compilatore come una funzione c(), dove c(source)=compiled codee l'ambiente in esecuzione come r(), dove r(compiled code)=program output, quindi per determinare l'output per qualsiasi codice sorgente è necessario calcolare il valore di r(c(source code)). Se il calcolo c()richiede la conoscenza del valore di r(c())per qualsiasi input, non è necessario un separato r()e c(): si può semplicemente derivare una funzione i()da c()tale i(source)=program output.


2

Altri hanno commentato il problema dell'arresto e così via. Questi si applicano generalmente a porzioni di funzioni. Tuttavia, può essere difficile / impossibile sapere se viene utilizzato o meno un intero tipo (classe / ecc.).

In .NET / Java / JavaScript e altri ambienti basati su runtime non c'è nulla che impedisca il caricamento dei tipi tramite reflection. Questo è popolare tra i framework di iniezione delle dipendenze ed è ancora più difficile da ragionare di fronte alla deserializzazione o al caricamento dinamico dei moduli.

Il compilatore non può sapere se tali tipi verrebbero caricati. I loro nomi potrebbero derivare da file di configurazione esterni in fase di esecuzione.

È possibile che si cerchi di scuotere l'albero, che è un termine comune per strumenti che tentano di rimuovere in modo sicuro i sottogrammi di codice non utilizzati.


Non conosco Java e javascript, ma .NET in realtà ha un plug-in resharper per quel tipo di rilevamento DI (chiamato Agent Mulder). Naturalmente, non sarà in grado di rilevare i file di configurazione, ma è in grado di rilevare confit nel codice (che è molto più popolare).
Lega il

2

Prendi una funzione

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

Puoi provare che actnumbernon sarà mai 2così che Action2()non si chiama mai ...?


7
Se riesci ad analizzare i chiamanti della funzione, allora potresti esserlo, sì.
circa

2
@abligh Ma il compilatore di solito non può analizzare tutto il codice chiamante. Comunque, anche se potesse, l'analisi completa potrebbe richiedere solo una simulazione di tutti i possibili flussi di controllo, il che è quasi sempre semplicemente impossibile a causa delle risorse e del tempo necessario. Quindi, anche se teoricamente esiste una prova che " Action2()non verrà mai chiamato", è impossibile dimostrare il reclamo in pratica - non può essere completamente risolto da un compilatore . La differenza è come "esiste un numero X" rispetto a "possiamo scrivere il numero X in decimale". Per alcuni X quest'ultimo non accadrà mai, sebbene il primo sia vero.
CiaPan,

Questa è una risposta scadente. le altre risposte dimostrano che è impossibile sapere se actnumber==2. Questa risposta afferma semplicemente che è difficile senza nemmeno affermare una complessità.
Salterio,

1

Non sono d'accordo sul problema dell'arresto. Non definirei tale codice morto anche se in realtà non verrà mai raggiunto.

Invece, consideriamo:

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();
}

(Ignora gli errori di tipo e overflow) Codice morto?


2
L'ultimo teorema di Fermat è stato dimostrato nel 1994. Quindi una corretta implementazione del tuo metodo non avrebbe mai eseguito FermatWasWrong. Sospetto che la tua implementazione eseguirà FermatWasWrong, perché puoi raggiungere il limite di precisione dei float.
Taemyr,

@Taemyr Aha! Questo programma non verifica correttamente l'ultimo teorema di Fermat; un controesempio per ciò che fa test è N = 3, A = 65536, B = 65536 (che fornisce Test = 0)
user253751

@immibis Sì, mi sono perso che traboccerà int prima che la precisione sui galleggianti diventi un problema.
Taemyr,

@immibis Nota la parte inferiore del mio post: ignora gli errori di tipo e overflow. Stavo prendendo quello che pensavo fosse un problema irrisolto come base di una decisione - so che il codice non è perfetto. È comunque un problema che non può essere forzato brutalmente.
Loren Pechtel,

-1

Guarda questo esempio:

public boolean isEven(int i){

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

Il compilatore non può sapere che un int può essere solo pari o dispari. Pertanto, il compilatore deve essere in grado di comprendere la semantica del codice. Come dovrebbe essere implementato? Il compilatore non può garantire che il rendimento più basso non verrà mai eseguito. Pertanto il compilatore non è in grado di rilevare il codice morto.


1
Umm, davvero? Se lo scrivo in C # + ReSharper ottengo un paio di suggerimenti. Seguirli finalmente mi dà il codice return i%2==0;.
Thomas Weller,

10
Il tuo esempio è troppo semplice per essere convincente. Il caso specifico di i % 2 == 0e i % 2 != 0non richiede nemmeno un ragionamento sul valore di un modulo intero una costante (che è ancora facile da fare), richiede solo l'eliminazione della sottoespressione comune e il principio generale (canonicalizzazione, anche) che if (cond) foo; if (!cond) bar;può essere semplificato if (cond) foo; else bar;. Naturalmente la "comprensione della semantica" è un problema molto difficile, ma questo post non mostra né lo è, né mostra che la risoluzione di questo problema è necessaria per il rilevamento del codice morto.

5
Nel tuo esempio, un compilatore di ottimizzazione individuerà la sottoespressione comune i % 2e la estrarrà in una variabile temporanea. Riconoscerà quindi che le due ifistruzioni si escludono a vicenda e possono essere scritte come if(a==0)...else..., quindi individuerà che tutti i possibili percorsi di esecuzione passano attraverso le prime due returnistruzioni e quindi la terza returnistruzione è un codice morto. (Un buon compilatore di ottimizzazione è ancora più aggressivo: GCC ha trasformato il mio codice di test in una coppia di operazioni di manipolazione dei bit).
Segna il

1
Questo esempio è buono per me. Rappresenta il caso in cui un compilatore non è a conoscenza di alcune circostanze concrete. Lo stesso vale per if (availableMemory()<0) then {dead code}.
Little Santi,

1
@LittleSanti: In realtà, GCC rileverà che tutto ciò che hai scritto è un codice morto! Non è solo la {dead code}parte. GCC lo scopre dimostrando che è inevitabile un overflow di numeri interi con segno. Tutto il codice su quell'arco nel grafico di esecuzione è quindi un codice morto. GCC può persino rimuovere il ramo condizionale che porta a quell'arco.
MSalters 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.