it-swarm.com.de

Warum ist es nicht möglich, einen Compiler zu erstellen, der bestimmen kann, ob eine C++ - Funktion den Wert einer bestimmten Variablen ändert?

Ich habe diese Zeile in einem Buch gelesen: 

Es ist nachweislich unmöglich, einen Compiler zu erstellen, der tatsächlich Bestimmen Sie, ob eine C++ - Funktion den Wert einer .__ ändert. bestimmte Variable.

In diesem Abschnitt ging es darum, warum der Compiler bei der Überprüfung der Konstanz konservativ ist. 

Warum ist es unmöglich, einen solchen Compiler zu erstellen?  

Der Compiler kann immer überprüfen, ob eine Variable neu zugewiesen wird, eine Nicht-Konstante-Funktion darin aufgerufen wird oder ob sie als Nicht-Konstante-Parameter übergeben wird.

102
Cricketer

Warum ist es unmöglich, einen solchen Compiler zu erstellen?

Aus dem gleichen Grund, dass Sie kein Programm schreiben können, das bestimmt, ob ein bestimmtes Programm beendet wird. Dieses Problem wird als halting-Problem bezeichnet und ist eines dieser Dinge, die nicht berechenbar sind.

Um es klar zu sagen, können Sie einen Compiler schreiben, der feststellen kann, dass eine Funktion die Variable in einigen Fällen ändert. Sie können jedoch keinen Compiler schreiben, der zuverlässig sagt, dass die Funktion die Variable ändern wird oder nicht ( oder halt) für jede mögliche Funktion.

Hier ist ein einfaches Beispiel:

void foo() {
    if (bar() == 0) this->a = 1;
}

Wie kann ein Compiler anhand dieses Codes feststellen, ob sich foo jemals a ändern wird? Ob dies der Fall ist oder nicht, hängt von Bedingungen außerhalb der Funktion ab, nämlich der Implementierung von bar. Es gibt noch mehr als den Beweis dafür, dass das Halteproblem nicht berechenbar ist, aber es wird bereits in dem verlinkten Wikipedia-Artikel (und in jedem Lehrbuch der Berechnungstheorie) gut erklärt. Ich versuche es hier nicht richtig zu erklären.

136
Caleb

Stellen Sie sich einen solchen Compiler vor. Nehmen wir auch an, dass es zur Vereinfachung eine Bibliotheksfunktion enthält, die 1 zurückgibt, wenn die übergebene Funktion eine gegebene Variable ändert, und 0, wenn die Funktion dies nicht tut. Was soll dieses Programm dann drucken?

int variable = 0;

void f() {
    if (modifies_variable(f, variable)) {
        /* do nothing */
    } else {
        /* modify variable */
        variable = 1;
    }
}

int main(int argc, char **argv) {
    if (modifies_variable(f, variable)) {
        printf("Modifies variable\n");
    } else {
        printf("Does not modify variable\n");
    }

    return 0;
}
121
orlp

Verwechseln Sie nicht "wird oder wird eine Variable nicht ändern, wenn diese Eingaben" für "einen Ausführungspfad haben, der eine Variable ändert."

Ersteres heißt ndurchsichtige Prädikatenbestimmung und ist trivial unmöglich zu entscheiden - abgesehen von der Reduzierung des Halteproblems können Sie nur darauf hinweisen, dass die Eingaben möglicherweise von einer unbekannten Quelle stammen (z. B. vom Benutzer). Dies gilt für alle Sprachen, nicht nur für C++.

Die letztere Anweisung kann jedoch durch Betrachten des Analysebaums bestimmt werden, was alle optimierenden Compiler tun. Der Grund dafür ist, dass reine Funktionen (und referenziell transparent Funktionen für eine Definition von referenziell transparent ) alle Arten von Nice-Optimierungen haben, die angewendet werden können, wie zum Beispiel leicht inlinierbar zu sein oder ihre Werte zur Kompilierungszeit bestimmen zu lassen; Aber um zu wissen, ob eine Funktion rein ist, müssen wir wissen, ob sie jemals eine Variable modifizieren kann .

Was also eine überraschende Aussage über C++ zu sein scheint, ist eine triviale Aussage über alle Sprachen.

Ich denke, das Schlüsselwort in "ob eine C++ - Funktion den Wert einer bestimmten Variablen" ändert oder nicht "ist" wird ". Es ist durchaus möglich, einen Compiler zu erstellen, der prüft, ob eine C++ - Funktion erlaubt ist den Wert einer bestimmten Variablen ändert. Sie können nicht mit Sicherheit sagen, dass die Änderung stattfindet:

void maybe(int& val) {
    cout << "Should I change value? [Y/N] >";
    string reply;
    cin >> reply;
    if (reply == "Y") {
        val = 42;
    }
}
28
dasblinkenlight

Ich denke nicht, dass es notwendig ist, das Halteproblem aufzurufen, um zu erklären, dass Sie zur Kompilierzeit nicht algorithmisch wissen können, ob eine bestimmte Funktion eine bestimmte Variable ändert oder nicht.

Stattdessen genügt der Hinweis, dass das Verhalten einer Funktion häufig von Laufzeitbedingungen abhängt, über die der Compiler vorher nicht Bescheid wissen kann. Z.B.

int y;

int main(int argc, char *argv[]) {
   if (argc > 2) y++;
}

Wie kann der Compiler mit Sicherheit vorhersagen, ob y geändert wird?

15
LarsH

Es ist machbar und Compiler machen es ständig für einige Funktionen , dies ist beispielsweise eine triviale Optimierung für einfache Inline-Accessoren oder viele reine Funktionen. 

Was unmöglich ist, ist es im allgemeinen Fall zu wissen.

Wann immer ein Systemaufruf oder ein Funktionsaufruf von einem anderen Modul kommt oder ein Aufruf einer möglicherweise überschriebenen Methode, kann alles passieren, einschließlich der feindlichen Übernahme durch einen Hacker, der einen Stapelüberlauf zum Ändern einer nicht verwandten Variablen verwendet.

Verwenden Sie jedoch const, vermeiden Sie globale Variablen, ziehen Sie Verweise auf Zeiger vor, vermeiden Sie die Wiederverwendung von Variablen für nicht zusammenhängende Aufgaben usw., wodurch das Leben des Compilers bei aggressiven Optimierungen einfacher wird.

7
kriss

Es gibt mehrere Möglichkeiten, dies zu erklären. Eine davon ist das Halting Problem :

In der Berechenbarkeitstheorie kann das Halteproblem wie folgt festgestellt werden: "Bei einer Beschreibung eines beliebigen Computerprogramms entscheiden Sie, ob das Programm ausgeführt wird oder für immer weiterläuft". Dies ist gleichbedeutend mit dem Problem, bei einem Programm und einer Eingabe zu entscheiden, ob das Programm schließlich anhält, wenn es mit dieser Eingabe ausgeführt wird, oder für immer ausgeführt wird.

Alan Turing bewies 1936, dass es keinen allgemeinen Algorithmus gibt, um das Halteproblem für alle möglichen Programmeingabe-Paare zu lösen.

Wenn ich ein Programm schreibe, das so aussieht:

do tons of complex stuff
if (condition on result of complex stuff)
{
    change value of x
}
else
{
    do not change value of x
}

Ändert sich der Wert von x? Um dies festzustellen, müssen Sie zunächst feststellen, ob der do tons of complex stuff-Teil die Bedingung auslöst - oder noch grundlegender, ob er stoppt. Das kann der Compiler nicht.

6
Timothy Shields

Wirklich überrascht, dass es keine Antwort gibt, wenn man das Halteproblem direkt verwendet! Es gibt eine sehr einfache Reduktion von diesem Problem auf das Halteproblem. 

Stellen Sie sich vor, der Compiler könnte feststellen, ob eine Funktion den Wert einer Variablen geändert hat oder nicht. Dann würde es sicherlich in der Lage sein zu sagen, ob die folgende Funktion den Wert von y ändert oder nicht, vorausgesetzt, der Wert von x kann in allen Aufrufen des restlichen Programms verfolgt werden:

foo(int x){
   if(x)
       y=1;
}

Nun, für jedes Programm, das wir mögen, schreiben wir es wie folgt um:

int y;
main(){
    int x;
    ...
    run the program normally
    ...
    foo(x);
}

Beachten Sie, dass, wenn und nur wenn unser Programm den Wert von y ändert, das Programm dann beendet wird - foo () ist das letzte, was es vor dem Beenden tut. Das heißt, wir haben das Halteproblem gelöst!

Die obige Reduktion zeigt, dass das Problem der Bestimmung, ob sich der Wert einer Variablen ändert, mindestens so groß ist wie das Halteproblem. Es ist bekannt, dass das Halteproblem inkompatibel ist, also muss dieses Problem auch sein.

6
John Doucette

Sobald eine Funktion eine andere Funktion aufruft, von der der Compiler die Quelle nicht "sieht", muss sie entweder davon ausgehen, dass die Variable geändert wurde, oder es kann weiter unten etwas schief gehen. Angenommen, wir haben folgendes in "foo.cpp":

 void foo(int& x)
 {
    ifstream f("f.dat", ifstream::binary);
    f.read((char *)&x, sizeof(x));
 }

und wir haben dies in "bar.cpp":

void bar(int& x)
{
  foo(x);
}

Wie kann der Compiler "wissen", dass sich x in bar nicht ändert (oder IS ändert sich passender)?

Ich bin sicher, wir können uns etwas komplexeres einfallen lassen, wenn dies nicht komplex genug ist.

4
Mats Petersson

Es ist im Allgemeinen unmöglich, dass der Compiler ermittelt, ob die Variable will geändert wird.

Bei der Überprüfung der Konstanz scheint die Frage von Interesse zu sein, ob die Variable can von einer Funktion geändert wird. Auch das ist schwer in Sprachen, die Zeiger unterstützen. Sie können nicht steuern, was anderer Code mit einem Zeiger macht, er könnte sogar von einer externen Quelle gelesen werden (obwohl dies unwahrscheinlich ist). In Sprachen, die den Zugriff auf den Speicher einschränken, können diese Arten von Garantien möglich sein und eine aggressivere Optimierung ermöglichen als in C++.

1
Krumelur

Um die Frage genauer zu fassen, schlage ich vor, dass der Autor des Buches möglicherweise die folgenden Einschränkungen hatte:

  1. Angenommen, der Compiler untersucht das Verhalten einer bestimmten Funktion in Bezug auf die Konstante einer Variablen. Zur Korrektheit müsste ein Compiler annehmen (wegen Aliasing wie nachstehend erklärt), wenn die Funktion eine andere Funktion aufgerufen hat, die Variable geändert wird. Die Annahme # 1 gilt also nur für Codefragmente, die keine Funktionsaufrufe durchführen.
  2. Angenommen, die Variable wird nicht durch eine asynchrone oder gleichzeitige Aktivität geändert.
  3. Angenommen, der Compiler bestimmt nur, ob die Variable geändert werden kann, nicht ob sie geändert wird. Der Compiler führt also nur eine statische Analyse durch.
  4. Angenommen, der Compiler berücksichtigt nur korrekt funktionierenden Code (keine Array-Überschreitungen/-Unterlaufs, fehlerhafte Zeiger usw.).

Ich denke, im Zusammenhang mit dem Compiler-Design sind Annahmen 1, 3, 4 aus Sicht eines Compilerschreibers im Zusammenhang mit der Codegenauigkeit und/oder der Codeoptimierung vollkommen sinnvoll. Annahme 2 macht Sinn, wenn das volatile Keyword fehlt. Und diese Annahmen stellen auch die Frage in den Vordergrund, um die Beantwortung einer vorgeschlagenen Antwort wesentlich definitiver zu machen :-) 

Unter diesen Annahmen ist ein wesentlicher Grund dafür, dass Konstanz nicht angenommen werden kann, auf das Variablen-Aliasing. Der Compiler kann nicht wissen, ob eine andere Variable auf die const-Variable verweist. Aliasing könnte auf eine andere Funktion in derselben Compilierungseinheit zurückzuführen sein. In diesem Fall könnte der Compiler Funktionen durchsuchen und mithilfe eines Aufrufbaums statisch ermitteln, dass Aliasing auftreten könnte. Wenn das Aliasing jedoch auf eine Bibliothek oder einen anderen Fremdcode zurückzuführen ist, hat der Compiler beim Funktionseintrag keine Möglichkeit zu wissen, ob Variablen einen Aliasing haben.

Sie könnten argumentieren, dass eine Variable/ein Argument als const gekennzeichnet ist und nicht per Aliasing geändert werden darf. Für einen Compiler-Writer ist dies jedoch sehr riskant. Es kann sogar riskant sein, dass ein menschlicher Programmierer eine Variable const als Teil deklariert, beispielsweise in einem großen Projekt, in dem er nicht das Verhalten des gesamten Systems oder des Betriebssystems oder einer Bibliothek kennt, um eine gewonnene Variable wirklich zu kennen. ' t ändern.

1
Χpẘ

Selbst wenn eine Variable als const deklariert ist, bedeutet dies nicht, dass sie durch einen schlecht geschriebenen Code überschrieben werden kann.

//   g++ -o foo foo.cc

#include <iostream>
void const_func(const int&a, int* b)
{
   b[0] = 2;
   b[1] = 2;
}

int main() {
   int a = 1;
   int b = 3;

   std::cout << a << std::endl;
   const_func(a,&b);
   std::cout << a << std::endl;
}

ausgabe:

1
2
0
Mark Lakata

Um auf meine Kommentare einzugehen, ist der Text dieses Buches unklar, wodurch das Problem verschleiert wird.

Wie ich kommentiert habe, versucht das Buch zu sagen: "Lass uns unendlich viele Affen dazu bringen, jede denkbare C++ - Funktion zu schreiben, die jemals geschrieben werden könnte. verwendet, können wir nicht herausfinden, ob die Funktion diese Variable ändert. "

Für einige (sogar viele) Funktionen in einer bestimmten Anwendung kann dies natürlich vom Compiler bestimmt werden und ist sehr einfach. Aber nicht für alle (oder notwendigerweise die meisten).

Diese Funktion kann leicht analysiert werden:

static int global;

void foo()
{
}

"foo" ändert eindeutig nicht "global". Es ändert überhaupt nichts und ein Compiler kann das sehr leicht herausfinden.

Diese Funktion kann nicht so analysiert werden:

static int global;

int foo()
{
    if ((Rand() % 100) > 50)
    {
        global = 1;
    }
    return 1;

Da die Aktionen von "foo" von einem Wert abhängen, der zur Laufzeit ändern kann, kann nicht eindeutig zur Kompilierzeit bestimmt werden, ob er "global" ändert.

Dieses ganze Konzept ist viel einfacher zu verstehen, als dies von Informatikern behauptet wird. Wenn die Funktion auf der Grundlage von Dingen etwas anderes zur Laufzeit ändern kann, können Sie nicht herausfinden, was sie tun wird, bis sie ausgeführt wird. Jedes Mal, wenn sie ausgeführt wird, kann sie etwas anderes machen. Ob es nachweislich unmöglich ist oder nicht, ist offensichtlich unmöglich.

0
El Zorko