2016-05-25 4 views
14
#include <stdio.h> 
int main(void) { 
    int i; 
    scanf("%d", &i); 
    if(i != 30) { return(0); } 
    printf("i is equal to %d\n", i); 
} 

Es scheint, dass die resultierende Zeichenfolge wird es immer sein „ist i gleich 30“, so, warum GCC diesen Anruf nicht optimiert mit einem Aufruf an puts() printf oder write(), zum Beispiel?Warum optimiert GCC diesen Aufruf von printf nicht?

(überprüft einfach die generierte Assembly, mit gcc -O3 (Version 5.3.1) oder auf den Godbolt Compiler Explorer)

+1

'gcc' kann nicht (kann nicht ..?) Die' printf' Ausgabe vorhersagen. – LPs

+1

@LPs AFAIK, ändert er Aufrufe von printf() mit Aufrufen von puts() und putchar() wo möglich. – Mitsos101

+3

@ Mitsos101 nur mit * bekannten Kompilierzeitkonstanten *. Was Sie in Ihrem Code sehen, kann nur durch Ausführen ermittelt werden. Einfach für Sie in Ihrem Kopf - weniger einfach für einen Compiler. – adelphus

Antwort

11

Vor allem ist das Problem nicht die if; wie Sie gesehen haben, gcc sieht durch die if und schafft es, 30 direkt an printf übergeben.

Nun tut gcc eine gewisse Logik haben auf Sonderfälle von printf handhaben (insbesondere, tut es printf("something\n") optimieren und sogar printf("%s\n", "something") zu puts("something")), aber es ist sehr spezifisch und nicht viel weiter geht; printf("Hello %s\n", "world") wird zum Beispiel so belassen wie es ist. Noch schlimmer ist, dass eine der Varianten über ohne eine nachlaufende Zeilenschaltung unberührt bleibt, selbst wenn sie in umgewandelt werden könnten.

Ich kann mir vorstellen, dass dies auf zwei Hauptprobleme kommt:

  • die beiden oben genannten Fälle sind extrem einfache Muster zu implementieren und sehr oft passieren, aber für den Rest der Mühe ist es wahrscheinlich selten wert; Wenn die Zeichenkette konstant ist und die Leistung wichtig ist, kann sich der Programmierer leicht darum kümmern - wenn die Leistung von printf kritisch ist, sollte er sich nicht auf diese Art der Optimierung verlassen, die bei der geringsten Änderung des Formats brechen kann Zeichenfolge.

    Wenn Sie mich fragen, auch nur die puts Optimierungen oben sind bereits "für die Stil Punkte gehen", werden Sie wirklich keine ernsthafte Leistung in nichts als künstliche Testfälle gewinnen.

  • Wenn Sie beginnen, außerhalb des Bereichs von %s\n zu gehen, ist printf ein Minenfeld, da es eine starke Abhängigkeit von der Laufzeitumgebung hat; insbesondere sind viele printf Spezifizierer (leider) vom Gebietsschema betroffen, außerdem gibt es eine Aufzählung von implementierungsspezifischen Macken und Spezifizierern (und gcc kann mit printf von glibc, musl, mingw/msvcrt arbeiten, ...- und zur Kompilierzeit können Sie die Ziel-C-Laufzeit nicht aufrufen - denken Sie beim Cross-Compilieren nach.

    Ich stimme zu, dass diese einfache %d Fall wahrscheinlich sicher ist, aber ich kann sehen, warum sie wahrscheinlich beschlossen, zu vermeiden, übermäßig schlau zu sein und nur die dümmsten und sichersten Optimierungen hier durchzuführen.


Für den neugierigen Leser, here ist, wo diese Optimierung tatsächlich umgesetzt wird; Wie Sie sehen können, passt die Funktion zu einer begrenzten Anzahl von sehr einfachen Fällen (und GIMPLE beiseite, hat sich nicht viel geändert, seit this nice article sie beschrieben wurde). Übrigens erklärt die Quelle tatsächlich, warum sie die fputs-Variante für den Nicht-Newline-Fall nicht implementieren konnten (es gibt keinen einfachen Weg, die stdout global in dieser Kompilierungsstufe zu referenzieren).

+1

Sie haben Ihr Beispiel korrigiert (das nicht mit einem Zeilenumbruch endete), bevor ich diesen Kommentar abgeschlossen habe, aber ich werde diesen [godbolt-Link trotzdem] (https://godbolt.org/g/mGzqZQ) veröffentlichen, wo ich v3 hinzugefügt habe und v4 zu einem Beispiel, das ich bereits für etwas anderes gemacht hatte. –

6

Moderne Compiler sind ziemlich schlau, aber nicht klug genug, um die Ausgabe mit Logik voraussehen. In diesem Fall ist es für menschliche Programmierer recht einfach, diesen Code zu optimieren, aber diese Aufgabe ist für Maschinen zu schwierig. Tatsächlich ist das Vorhersagen der Ausgabe eines Programms, ohne es auszuführen, für Programme unmöglich (gcc zum Beispiel). Zum Nachweis siehe halting problem.

Wie auch immer, Sie erwarten nicht, dass alle Programme ohne Eingaben auf mehrere puts()-Anweisungen optimiert werden. Daher ist es für GCC durchaus sinnvoll, diesen Code mit einer scanf()-Anweisung nicht zu optimieren.

Dies bedeutet jedoch nicht, dass Compiler nicht optimiert werden können oder sollten, um optimierte Dateien zu generieren. Obwohl es unmöglich ist, das Ergebnis alle Programme vorherzusagen, ist es möglich, viele von ihnen zu verbessern.

+1

in diesem Beispiel ersetzt der Compiler tatsächlich ich mit 30, also kümmert es sich nicht um die Eingabe. Es ist einfach nicht schlau genug, um zu wissen, dass es printf mit puts ersetzen kann – pm100

+0

"Programmanalyse kann nie perfekt sein wegen des Halteproblems, also sollten wir uns nicht darum kümmern" ist solch eine lächerliche Einstellung. Die Vorhersage der Ausgabe von Programmen ist * im Allgemeinen unmöglich *. Das bedeutet nicht, dass Sie bestimmte Fälle nicht vorhersagen können (oder sollten). Wie bereits erwähnt wurde, ist GCC (genau wie jeder ordentliche Compiler) durchaus in der Lage, vorauszusagen, dass "i" 30 ist und tatsächlich den darauf basierenden Code optimiert. – sepp2k

1

Nicht sicher, ob dies eine überzeugende Antwort ist, aber ich würde erwarten, dass Compiler printf("%d\n", 10) Fall zu puts("10") nicht optimieren sollten.

Warum? Weil dieser Fall komplizierter sein könnte als Sie denken. Hier sind einige der Probleme, die ich von im Moment denken kann:

  1. Convert Binärzahlen in ASCII erhöht Größe Stringliteral und damit die Gesamtcodegröße. Obwohl dies für kleine Zahlen irrelevant ist, aber wenn es printf("some number: %d", 10000) ---- 5 Ziffern oder mehr ist (unter der Annahme int ist 32-Bit), wird die String-Größe erhöht die Größe für die ganze Zahl gespeichert, und einige Leute könnten dies als einen Nachteil betrachten . Ja, mit der Konvertierung habe ich eine "push to stack" Anweisung gespeichert, aber wie viele Bytes die Anweisung ist und wie viele gespeichert werden, ist architekturspezifisch. Es ist nicht trivial für einen Compiler zu sagen, ob es das wert ist.

  2. Auffüllung, wenn in Formaten verwendet, kann auch die Größe des erweiterten Zeichenfolgeliterals erhöht werden. Beispiel: printf("some number: %10d", 100)

  3. Manchmal ist der Entwickler ein Format-String unter printf Anrufen teilen würde, für die Code-Größe Gründe:

    printf("%-8s: %4d\n", "foo", 100); 
    printf("%-8s: %4d\n", "bar", 500); 
    printf("%-8s: %4d\n", "baz", 1000); 
    printf("%-8s: %4d\n", "something", 10000); 
    

    sie Konvertieren in verschiedenen Stringliterale möglicherweise die Größe Vorteil verlieren.

  4. Für %f, %e und %g gibt es ein Problem, dass Dezimalpunkt "." ist länderabhängig. Daher kann der Compiler sie für Sie nicht auf eine String-Konstante erweitern. Obwohl wir nur über %d diskutieren, erwähne ich das hier der Vollständigkeit halber.

Verwandte Themen