Ich habe diese Zeile in einem Buch gelesen:
Es ist nachweislich unmöglich, einen Compiler zu erstellen, der tatsächlich bestimmen kann, ob eine C ++ - Funktion den Wert einer bestimmten Variablen ändert oder nicht.
In dem Absatz wurde darüber gesprochen, warum der Compiler bei der Überprüfung der Konstanz konservativ ist.
Warum ist es unmöglich, einen solchen Compiler zu erstellen?
Der Compiler kann jederzeit überprüfen, ob eine Variable neu zugewiesen wird, eine Nicht-Konstanten-Funktion darauf aufgerufen wird oder ob sie als Nicht-Konstanten-Parameter übergeben wird ...
c++
compiler-construction
Cricketspieler
quelle
quelle
Antworten:
Aus dem gleichen Grund, aus dem Sie kein Programm schreiben können, das bestimmt, ob ein bestimmtes Programm beendet wird. Dies ist als Stoppproblem bekannt und eines der Dinge, die nicht berechenbar sind.
Um klar zu sein, können Sie einen Compiler schreiben, der bestimmen kann, dass eine Funktion die Variable in einigen Fällen ändert , aber Sie können keinen schreiben, der Ihnen zuverlässig sagt, dass die Funktion die Variable (oder den Halt) für ändern wird oder nicht jede mögliche Funktion.
Hier ist ein einfaches Beispiel:
Wie kann ein Compiler anhand des Codes feststellen, ob
foo
sich dies jemals ändern wirda
? Ob dies der Fall ist oder nicht, hängt von den Bedingungen außerhalb der Funktion ab, nämlich der Implementierung vonbar
. Der Beweis, dass das Problem des Anhaltens nicht berechenbar ist, ist mehr als das, aber es wird bereits im verlinkten Wikipedia-Artikel (und in jedem Lehrbuch zur Berechnungstheorie) ausführlich erklärt, sodass ich hier nicht versuchen werde, es richtig zu erklären.quelle
Stellen Sie sich vor, ein solcher Compiler existiert. Nehmen wir außerdem an, dass der Einfachheit halber eine Bibliotheksfunktion bereitgestellt wird, die 1 zurückgibt, wenn die übergebene Funktion eine bestimmte Variable ändert, und 0, wenn die Funktion dies nicht tut. Was soll dieses Programm dann drucken?
quelle
f
die Variable geändert wird - nicht, ob sie die Variable ändern könnte. Diese Antwort ist richtig.modifies_variable
von der Compiler-Quelle zu kopieren / einzufügen , wodurch Ihr Argument vollständig zunichte gemacht wird. (unter der Annahme von Open Source, aber der Punkt sollte klar sein)Verwechseln Sie nicht "wird oder wird eine Variable nicht ändern, wenn diese Eingaben gegeben sind" für "hat einen Ausführungspfad, der eine Variable modifiziert".
Ersteres wird als undurchsichtige Prädikatenbestimmung bezeichnet und ist trivial unmöglich zu entscheiden. Abgesehen von der Reduzierung des Stoppproblems 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 Aussage kann jedoch durch Betrachten des Analysebaums bestimmt werden, was alle optimierenden Compiler tun. Der Grund dafür ist, dass reine Funktionen (und referenziell transparente Funktionen, für eine Definition von referenziell transparent ) alle möglichen netten Optimierungen aufweisen, die angewendet werden können, z. B. leicht inlinierbar zu sein oder ihre Werte zur Kompilierungszeit bestimmen zu lassen. aber wissen , ob eine Funktion rein ist, müssen wir wissen , ob es kann immer eine Variable ändern.
Was also als überraschende Aussage über C ++ erscheint, ist eigentlich eine triviale Aussage über alle Sprachen.
quelle
Ich denke, das Schlüsselwort in "ob eine C ++ - Funktion den Wert einer bestimmten Variablen ändert oder nicht" ist "wird". Es ist sicherlich möglich, einen Compiler zu erstellen, der prüft, ob eine C ++ - Funktion den Wert einer bestimmten Variablen ändern darf oder nicht. Sie können nicht mit Sicherheit sagen, dass die Änderung stattfinden wird:
quelle
const
-ness-Checks ging.Ich denke nicht, dass es notwendig ist, das Stoppproblem aufzurufen, um zu erklären, dass Sie zur Kompilierungszeit nicht algorithmisch wissen können, ob eine bestimmte Funktion eine bestimmte Variable ändert oder nicht.
Stattdessen reicht es aus, darauf hinzuweisen, dass das Verhalten einer Funktion häufig von Laufzeitbedingungen abhängt, die der Compiler nicht im Voraus kennen kann. Z.B
Wie kann der Compiler mit Sicherheit vorhersagen, ob
y
Änderungen vorgenommen werden?quelle
Dies ist möglich und Compiler tun dies für einige Funktionen ständig. 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.
Immer wenn ein Systemaufruf oder ein Funktionsaufruf von einem anderen Modul oder eine möglicherweise überschriebene Methode aufgerufen wird, kann alles passieren, einschließlich der feindlichen Übernahme durch die Verwendung eines Stapelüberlaufs durch einen Hacker zum Ändern einer nicht verwandten Variablen.
Sie sollten jedoch const verwenden, Globals vermeiden, Verweise auf Zeiger bevorzugen, die Wiederverwendung von Variablen für nicht verwandte Aufgaben usw. vermeiden, um das Leben des Compilers bei aggressiven Optimierungen zu vereinfachen.
quelle
Es gibt mehrere Möglichkeiten, dies zu erklären, von denen eine das Halteproblem ist :
Wenn ich ein Programm schreibe, das so aussieht:
Ändert sich der Wert von
x
? Um dies festzustellen, müssten Sie zuerst feststellen, ob dasdo tons of complex stuff
Teil den Zustand auslöst - oder noch grundlegender, ob es anhält. Das kann der Compiler nicht.quelle
Wirklich überrascht, dass es keine Antwort gibt, die das Stoppproblem direkt nutzt! Es gibt eine sehr einfache Reduzierung von diesem Problem auf das Stoppproblem.
Stellen Sie sich vor, der Compiler könnte feststellen, ob eine Funktion den Wert einer Variablen geändert hat oder nicht. Dann wäre es sicherlich möglich zu erkennen, ob die folgende Funktion den Wert von y ändert oder nicht, vorausgesetzt, der Wert von x kann in allen Aufrufen im Rest des Programms verfolgt werden:
Schreiben wir es für jedes Programm, das wir mögen, wie folgt um:
Beachten Sie, dass unser Programm, wenn und nur wenn es den Wert von y ändert, beendet wird - foo () ist das Letzte, was es vor dem Beenden tut. Das heißt, wir haben das Halteproblem gelöst!
Die obige Reduzierung zeigt uns, dass das Problem, festzustellen, ob sich der Wert einer Variablen ändert, mindestens so schwer ist wie das Stoppproblem. Es ist bekannt, dass das Stoppproblem inkompatibel ist, daher muss dies auch sein.
quelle
y
. Sieht für michfoo()
nach einer schnellen Rückkehr aus und geht dann wiedermain()
. (Außerdemfoo()
Sobald eine Funktion eine andere Funktion aufruft, deren Quelle der Compiler nicht "sieht", muss er entweder davon ausgehen, dass die Variable geändert wurde, oder es kann weiter unten ein Fehler auftreten. Angenommen, wir haben dies in "foo.cpp":
und wir haben dies in "bar.cpp":
Wie kann der Compiler "wissen", dass
x
sich nichts ändert (oder sich angemessener ändert)bar
?Ich bin sicher, wir können uns etwas Komplexeres einfallen lassen, wenn dies nicht komplex genug ist.
quelle
const_cast
in foo hinzufüge , würde es immer nochx
Änderungen vornehmen - ich würde gegen den Vertrag verstoßen, der besagt, dass Sie keineconst
Variablen ändern sollen , aber da Sie alles in "mehr const" konvertieren können undconst_cast
existieren, Die Designer der Sprache hatten sicherlich die Idee, dass es manchmal gute Gründe gibt zu glauben, dassconst
Werte geändert werden müssen.Es ist unmöglich , im Allgemeinen für die Compiler , um zu bestimmen , ob die Variable wird geändert werden, wurde als hingewiesen.
Wenn const-ness Überprüfung, scheint die Frage von Interesse zu sein , wenn die Variable kann durch eine Funktion geändert werden. Auch dies ist in Sprachen, die Zeiger unterstützen, schwierig. Sie können nicht steuern, was anderer Code mit einem Zeiger macht. Er kann 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 C ++.
quelle
Um die Frage genauer zu formulieren, schlage ich vor, dass der Autor des Buches möglicherweise folgende Einschränkungen im Sinn hatte:
Im Zusammenhang mit dem Compiler-Design halte ich die Annahmen 1,3,4 aus Sicht eines Compiler-Writers im Kontext der Code-Gen-Korrektheit und / oder Code-Optimierung für absolut sinnvoll. Annahme 2 ist sinnvoll, wenn das Schlüsselwort volatile nicht vorhanden ist. Und diese Annahmen fokussieren auch die Frage genug, um die Beurteilung einer vorgeschlagenen Antwort viel definitiver zu machen :-)
Angesichts dieser Annahmen ist ein Hauptgrund, warum Konstanz nicht angenommen werden kann, das variable Aliasing. Der Compiler kann nicht wissen, ob eine andere Variable auf die Variable const zeigt. Aliasing kann auf eine andere Funktion in derselben Kompilierungseinheit zurückzuführen sein. In diesem Fall kann der Compiler Funktionen durchsuchen und mithilfe eines Aufrufbaums statisch bestimmen, ob Aliasing auftreten kann. Wenn das Aliasing jedoch auf eine Bibliothek oder einen anderen Fremdcode zurückzuführen ist, kann der Compiler bei der Funktionseingabe nicht feststellen, ob Variablen ein Aliasing aufweisen.
Sie könnten argumentieren, dass eine Variable / ein Argument, die als const markiert ist, nicht durch Aliasing geändert werden sollte, aber für einen Compiler-Writer ist das ziemlich riskant. Es kann für einen menschlichen Programmierer sogar riskant sein, eine Variable const als Teil eines großen Projekts zu deklarieren, bei dem er das Verhalten des gesamten Systems oder des Betriebssystems oder einer Bibliothek nicht kennt, um eine gewonnene Variable wirklich zu kennen. ' t ändern.
quelle
Selbst wenn eine Variable deklariert ist
const
, bedeutet dies nicht, dass schlecht geschriebener Code sie überschreiben kann.Ausgabe:
quelle
a
undb
Stapelvariablen sind und sichb[1]
zufällig am selben Speicherort befinden wiea
.const
wenn alles beschriftet istconst
. Dies liegt daran, dass undefiniertes Verhalten Teil von C / C ++ ist. Ich habe versucht, einen anderen Weg zu finden, um seine Frage zu beantworten, anstatt das Problem des Stillstands oder externe menschliche Eingaben zu erwähnen.Um meine Kommentare zu erweitern, ist der Text dieses Buches unklar, was das Problem verschleiert.
Wie ich kommentierte, versucht dieses Buch zu sagen: "Lassen Sie uns eine unendliche Anzahl von Affen dazu bringen, jede denkbare C ++ - Funktion zu schreiben, die jemals geschrieben werden könnte. Es wird Fälle geben, in denen wir eine Variable auswählen, die (eine bestimmte Funktion, die die Affen geschrieben haben) verwendet, können wir nicht herausfinden, ob die Funktion diese Variable ändern wird. "
Natürlich kann dies für einige (sogar viele) Funktionen in einer bestimmten Anwendung vom Compiler sehr einfach bestimmt werden. Aber nicht für alle (oder unbedingt für die meisten).
Diese Funktion kann einfach so analysiert werden:
"foo" ändert "global" eindeutig nicht. Es ändert überhaupt nichts, und ein Compiler kann dies sehr einfach herausfinden.
Diese Funktion kann nicht so analysiert werden:
Da die Aktionen von "foo" von einem Wert abhängen, der sich zur Laufzeit ändern kann , kann zur Kompilierungszeit offenbar nicht festgestellt werden , ob "global" geändert wird.
Dieses ganze Konzept ist viel einfacher zu verstehen, als es Informatiker vermuten. Wenn die Funktion etwas anderes tun kann, basierend darauf, dass sich die Dinge zur Laufzeit ändern können, können Sie nicht herausfinden, was sie tun wird, bis sie ausgeführt wird, und jedes Mal, wenn sie ausgeführt wird, kann sie etwas anderes tun. Ob es nachweislich unmöglich ist oder nicht, es ist offensichtlich unmöglich.
quelle