Einige hypermoderne C-Compiler schließen daraus, dass solche Eingaben niemals empfangen werden, wenn ein Programm bei bestimmten Eingaben Undefiniertes Verhalten aufruft. Folglich kann jeder Code, der irrelevant wäre, wenn solche Eingaben nicht empfangen würden, eliminiert werden.
Als einfaches Beispiel gegeben:
void foo(uint32_t);
uint32_t rotateleft(uint_t value, uint32_t amount)
{
return (value << amount) | (value >> (32-amount));
}
uint32_t blah(uint32_t x, uint32_t y)
{
if (y != 0) foo(y);
return rotateleft(x,y);
}
Ein Compiler kann daraus schließen, dass die Funktion niemals mit Null aufgerufen wird , da die Auswertung von value >> (32-amount)
undefiniertes Verhalten amount
ergibt, wenn Null ist. Der Aufruf an kann somit bedingungslos gemacht werden.blah
y
foo
Soweit ich das beurteilen kann, scheint sich diese Philosophie irgendwann im Jahr 2010 durchgesetzt zu haben. Die frühesten Beweise, die ich für ihre Wurzeln gesehen habe, stammen aus dem Jahr 2009 und sind im C11-Standard verankert, der ausdrücklich besagt, dass undefiniertes Verhalten überhaupt auftritt Punkt in der Ausführung eines Programms wird das Verhalten des gesamten Programms rückwirkend undefiniert.
War die Vorstellung, dass Compiler versuchen sollten, undefiniertes Verhalten zu verwenden, um umgekehrte kausale Optimierungen zu rechtfertigen (dh das undefinierte Verhalten in der rotateleft
Funktion sollte den Compiler veranlassen, anzunehmen, blah
dass es mit einer Nicht-Null aufgerufen worden sein muss y
, unabhängig davon, ob irgendetwas jemals dazu führen würde oder nichty
zu einen Wert ungleich Null halten), der vor 2009 ernsthaft befürwortet wurde? Wann wurde so etwas zum ersten Mal ernsthaft als Optimierungstechnik vorgeschlagen?
[Nachtrag]
Einige Compiler haben sogar im 20. Jahrhundert Optionen aufgenommen, um bestimmte Arten von Rückschlüssen auf Schleifen und die darin berechneten Werte zu ermöglichen. Zum Beispiel gegeben
int i; int total=0;
for (i=n; i>=0; i--)
{
doSomething();
total += i*1000;
}
Ein Compiler kann es auch ohne die optionalen Schlussfolgerungen wie folgt umschreiben:
int i; int total=0; int x1000;
for (i=n, x1000=n*1000; i>0; i--, x1000-=1000)
{
doSomething();
total += x1000;
}
da das Verhalten dieses Codes genau mit dem Original übereinstimmen würde, selbst wenn der Compiler spezifizierte, dass int
Werte immer in mod-65536 Zwei-Komplement-Weise umbrochen werden . Die zusätzliche-Inferenz - Option lassen würde der Compiler erkennen , dass da i
und x1000
Null zugleich überqueren sollte, kann die ehemalige Variable eliminiert werden:
int total=0; int x1000;
for (x1000=n*1000; x1000 > 0; x1000-=1000)
{
doSomething();
total += x1000;
}
Auf einem System, auf dem int
Werte in Mod 65536 eingeschlossen sind, würde ein Versuch, eine der ersten beiden Schleifen mit n
33 auszuführen , dazu führen, doSomething()
dass 33 Mal aufgerufen wird. Im Gegensatz dazu würde die letzte Schleife überhaupt nicht aufrufen doSomething()
, obwohl der erste Aufruf von tatsächlich von Vorteil wäre. Darüber hinaus entschuldigte sich die Compiler-Dokumentation in der Regel dafür, dass sie das Verhalten von Programmen ändern würde - selbst von Programmen, die sich mit UB befassten.doSomething()
einem arithmetischen Überlauf vorausgegangen wäre. Ein solches Verhalten könnte als "nicht kausal" angesehen werden, aber die Auswirkungen sind ziemlich gut eingeschränkt, und es gibt viele Fälle, in denen das Verhalten nachweislich harmlos wäre (in Fällen, in denen eine Funktion erforderlich ist, um einen Wert zu liefern, wenn eine Eingabe gegeben wird, aber der Wert kann beliebig sein, wenn die Eingabe ungültig ist, und die Schleife wird schneller beendet, wenn ein ungültiger Wert von angegeben wirdn
Ich bin daran interessiert, wann sich die Einstellungen von Compiler-Autoren von der Idee abwandten, dass Plattformen, wenn dies praktikabel ist, einige verwendbare Verhaltensbeschränkungen auch in Fällen dokumentieren sollten, die nicht vom Standard vorgeschrieben sind, zu der Idee, dass Konstrukte, die auf Verhaltensweisen beruhen, die nicht von der Norm vorgeschrieben sind Standard sollte als unzulässig eingestuft werden, selbst wenn er auf den meisten vorhandenen Compilern genauso gut oder besser funktioniert als jeder streng konforme Code, der dieselben Anforderungen erfüllt (häufig sind Optimierungen möglich, die bei streng konformem Code nicht möglich wären).
shape->Is2D()
, dass ein Objekt aufgerufen wurde, das nicht abgeleitet wurde von Shape2D
. Es gibt einen großen Unterschied zwischen der Optimierung von Code, der nur relevant wäre, wenn bereits ein kritisches undefiniertes Verhalten
Shape2D::Is2D
ist eigentlich besser , immer zu springen, als das Programm es verdient.
int prod(int x, int y) {return x*y;}
hätte ausgereicht. Die strikte Einhaltung von" Nukes nicht starten "würde jedoch Code erfordern, der schwerer zu lesen ist und fast sicherlich viel langsamer auf vielen Plattformen laufen.