Ich habe kürzlich etwas gefunden, das den folgenden Zeilen ähnelt:
#include <string>
// test if the extension is either .bar or .foo
bool test_extension(const std::string& ext) {
return ext == ".bar" || ".foo";
// it obviously should be
// return ext == ".bar" || ext == ".foo";
}
Die Funktion macht offensichtlich nicht das, was der Kommentar vorschlägt. Aber darum geht es hier nicht. Bitte beachten Sie, dass dies kein Duplikat von Können Sie 2 oder mehr ODER-Bedingungen in einer if-Anweisung verwenden? da ich mir voll bewusst bin, wie du die Funktion richtig schreiben würdest!
Ich begann mich zu fragen, wie ein Compiler dieses Snippet behandeln könnte. Meine erste Intuition wäre gewesen, dass dies im return true;
Grunde genommen kompiliert würde . Das Einstecken des Beispiels in Godbolt zeigte, dass weder GCC 9.2 noch Clang 9 diese Optimierung mit Optimierung durchführen -O2
.
Ändern des Codes in 1
#include <string>
using namespace std::string_literals;
bool test_extension(const std::string& ext) {
return ext == ".bar"s || ".foo";
}
scheint den Trick zu tun, da die Versammlung jetzt im Wesentlichen ist:
mov eax, 1
ret
Meine Kernfrage lautet also: Gibt es etwas, das ich verpasst habe und das es einem Compiler nicht ermöglicht, beim ersten Snippet dieselbe Optimierung vorzunehmen?
1 Mit ".foo"s
dieser würde nicht einmal kompilieren, da der Compiler nicht konvertieren will eine std::string
zu bool
;-)
Bearbeiten
Der folgende Code wird ebenfalls "richtig" optimiert für return true;
:
#include <string>
bool test_extension(const std::string& ext) {
return ".foo" || ext == ".bar";
}
string::compare(const char*)
es einige Nebenwirkungen, die der Compiler nicht beseitigen wird (dieoperator==(string, string)
es nicht gibt)? Scheint unwahrscheinlich, aber der Compiler hat bereits festgestellt, dass das Ergebnismov eax, 1
ret
auch für das erste Snippet immer wahr ist (auch hat ).operator==(string const&, string const&)
ist,noexcept
während dasoperator==(string const&, char const*)
nicht ist? Ich habe jetzt keine Zeit, das weiter zu graben.foo || ext == ".bar"
wird der Anruf weg optimiert (siehe Bearbeiten). Widerspricht das Ihrer Theorie?a || b
bedeutet "Ausdruckb
nur bewerten, wenn Ausdrucka
istfalse
". Es ist orthogonal zur Laufzeit oder zur Kompilierungszeit.true || foo()
kann optimiert werdentrue
, auch wennfoo()
Nebenwirkungen auftreten, da (egal ob optimiert oder nicht) die rechte Seite niemals bewertet wird. Aberfoo() || true
kann nicht optimiert werden ,true
es sei denn der Compiler nachweisen kann , dass Berufungfoo()
keine feststellbaren Nebenwirkungen hat.xor eax,eax
obwohl er ohne diese Option die Funktion zum Vergleichen von Zeichenfolgen aufruft. Ich habe keine Ahnung, was ich davon halten soll.Antworten:
Dies wird Ihren Kopf noch mehr verwirren: Was passiert, wenn wir einen benutzerdefinierten Zeichentyp erstellen
MyCharT
und ihn verwenden, um unseren eigenen benutzerdefinierten Typ zu erstellenstd::basic_string
?Sicherlich kann ein benutzerdefinierter Typ nicht einfacher optimiert werden als ein einfacher
char
, oder?Hier ist die resultierende Baugruppe:
Sehen Sie es live!
Was ist der Unterschied zwischen meinem "benutzerdefinierten" Zeichenfolgentyp und
std::string
?Optimierung kleiner Zeichenfolgen
Zumindest unter GCC wird Small String Optimization tatsächlich in die Binärdatei für libstdc ++ kompiliert. Dies bedeutet, dass der Compiler während der Kompilierung Ihrer Funktion keinen Zugriff auf diese Implementierung hat und daher nicht wissen kann, ob Nebenwirkungen auftreten. Aus diesem Grund kann der Anruf nicht
compare(char const*)
entfernt werden. Unsere "benutzerdefinierte" Klasse hat dieses Problem nicht, da SSO nur für Plain implementiert iststd::string
.Übrigens, wenn Sie mit kompilieren
-std=c++2a
, optimiert der Compiler es weg . Ich bin leider noch nicht geschickt genug in C ++ 20, um zu wissen, welche Änderungen dies möglich gemacht haben.quelle