Warum lässt der GCC-Compiler Code weg?

Ich kann nicht verstehen, warum der GCC-Compiler einen Teil meines Codes ausschneidet, während er absolut denselben in der Nachbarschaft beibehält?

Der C-Code:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

Der entsprechende Teil von LSS (Assembler-Datei, erstellt vom Compiler):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Ich könnte davon ausgehen, dass der Compiler herausfindet, dass ein solcher Code ein Dummy ist, und ihn ausschneidet, aber warum wird am Ende des Codes derselbe beibehalten?

Gibt es Compiler-Anweisungen, um eine solche Optimierung zu verhindern?

Versuchen Sie, i auch als flüchtig zu deklarieren, dh unsigned volatile char i;
Danke, @VladimirCravero. Teilweise hat es funktioniert. Mein Code wurde jedoch erheblich größer, da var iim RAM zugewiesen wurde
Sie können den Compiler auch anweisen, keine einzelne Funktion zu optimieren, vielleicht lohnt es sich, diese Methode mit Ihrer ISR auszuprobieren. Siehe diese Frage zu Stackoverflow.
Hey Roman, ich habe deiner Frage das Tag "c" hinzugefügt, um atmega zu entfernen. Ich musste ein Tag entfernen, da es ein Limit (fünf) gibt, und wenn ich eine codebezogene Frage stelle, ist das Hinzufügen des Sprachnamens als Tag großartig, da der gesamte Code (Fragen und Antworten) hervorgehoben wird.
@VladimirCravero das ist in Ordnung! Danke für deinen Beitrag
Im Allgemeinen sind höhere Sprachen (wie C) explizit so konzipiert, dass sie nicht an eine 1:1-Beziehung mit ihrer resultierenden Assembly gebunden sind. Wenn Sie Anweisungen zählen müssen, um das richtige Timing zu finden, müssen Sie sich immer auf die Montage verlassen (wie einige der Antworten). Der springende Punkt bei Hochsprachen ist, dass der Compiler einige Freiheiten hat, um Ihren Code schneller, stärker und besser als zuvor zu machen. Details wie Registerzuweisungen und Verzweigungsvorhersagen sollten viel besser dem Compiler überlassen werden ... außer in Zeiten wie diesen, in denen Sie als Programmierer genau die gewünschten Anweisungen kennen.
Die bessere Frage ist, warum optimiert GCC diese beiden Schleifen nicht weg?
@IlmariKaronen auf jeden Fall!
Ich würde erwarten, dass es beide loswird oder vielleicht beide durch eine direkte Zuweisung auf Null ersetzt. Was sind die Definitionen der Makros?
@ Random832, ich habe der ursprünglichen Antwort eine Makrodefinition hinzugefügt. Andere sind sehr ähnlich.
Gcc entrollt zuerst die Schleife und merkt erst dann, dass der entsprechende Code nutzlos ist. Bei einer Schleifengröße von 30 wäre das Abrollen dumm und gcc tut es nicht. Auf einer höheren Optimierungsstufe werden beide wegoptimiert.
Das liegt an der Strömungsanalyse. Sie dekrementieren ivon 10bis 0, aber der aktualisierte Wert wird nie verwendet: Nach der Schleife gibt es ein define ( i = 30), aber keine Verwendung (wie y = i + 1). Was auch immer der Wert ivor dem i = 30Teil hatte, ist also nicht relevant. Die whileSchleife wird als sinnlos markiert und der entsprechende Code wird nicht ausgegeben.
@StefanoSanfilippo aber i ist eine lokale Variable und stirbt zwei Zeilen nach der zweiten Schleife und wird nie gelesen. Ich setze meine Chips auf Marcs Erklärung oben.

Antworten (4)

Da Sie in einem Kommentar angeben, dass "jeder CPU-Tick wertvoll ist", schlage ich vor, eine Inline-Assemblierung zu verwenden, um Ihre Verzögerungsschleife so zu gestalten, wie Sie es möchten. volatileDiese Lösung ist den verschiedenen oder überlegen, -O0weil sie deutlich macht, was Ihre Absicht ist.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Das sollte reichen. Das flüchtige Ding ist da, um dem Compiler zu sagen: "Ich weiß, dass das nichts bringt, behalte es einfach und vertraue mir". Die drei asm "Anweisungen" sind ziemlich selbsterklärend, Sie können jedes Register anstelle von r24 verwenden, ich glaube, der Compiler mag niedrigere Register, also sollten Sie vielleicht ein hohes verwenden. Nach dem ersten :sollten Sie c-Variablen für die Ausgabe (Lesen und Schreiben) auflisten, und es gibt keine, nach dem zweiten :sollten Sie c-Variablen für die Eingabe (nur) auflisten, wieder gibt es keine, und der dritte Parameter ist eine durch Kommas getrennte Liste geänderter Register , in diesem Fall r24. Ich bin mir nicht sicher, ob Sie auch das Statusregister einbeziehen sollten, da sich das ZEROFlag natürlich ändert, ich habe es nicht eingeschlossen.

Bearbeiten Sie die bearbeitete Antwort wie von OP angefordert. Einige Notizen.

Das "+rm"Before (i)bedeutet, dass Sie den Compiler entscheiden lassen, i in den Speicher oder in ein Register zu stellen. Das ist in den meisten Fällen eine gute Sache, da der Compiler besser optimieren kann, wenn er kostenlos ist. In Ihrem Fall glaube ich, dass Sie nur die r-Einschränkung beibehalten möchten, um i zu zwingen, ein Register zu sein.

Sieht so aus, als wäre das etwas, was ich wirklich brauche. Aber könnten Sie Ihre Antwort ändern, um eine beliebige cVariable anstelle des Literals zu akzeptieren, 10das ich in der ursprünglichen Antwort erwähnt habe? Ich versuche, die GCC-Handbücher bezüglich der richtigen Verwendung der Asm -Konstruktion zu lesen, aber das ist mir jetzt etwas unklar. Ich wäre sehr dankbar!
@RomanMatveev bearbeitet wie gewünscht

Sie könnten versuchen, die Schleife tatsächlich etwas tun zu lassen. So wie es aussieht, sagt der Compiler zu Recht "Diese Schleife macht nichts - ich werde sie los".

Sie könnten also ein Konstrukt ausprobieren, das ich häufig verwende:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Hinweis: Nicht alle Ziele für den gcc-Compiler verwenden dieselbe Inline-Assembly-Syntax – Sie müssen sie möglicherweise für Ihr Ziel anpassen.

Ihre Lösung scheint viel eleganter zu sein als meine ... vielleicht ist meine besser, wenn eine PRÄZISE Zyklenzählung erforderlich ist? Ich meine, es ist nicht garantiert, dass das ganze for-Ding auf eine bestimmte Weise kompiliert wird, oder?
Die einfache Tatsache, dass C verwendet wird, bedeutet, dass Sie keine Zyklen garantieren können. Wenn Sie eine präzise Zykluszählung benötigen, ist ASM der einzige Weg. Ich meine, Sie erhalten mit der C-Schleife ein anderes Timing, wenn Sie -funroll-loops aktiviert haben, als wenn Sie dies nicht tun usw.
Ja das ist, was ich dachte. Wenn Sie HW-Verzögerungen mit ausreichend hohen i-Werten (100 oder mehr) ausführen, liefert Ihre Lösung wahrscheinlich praktisch die gleichen Ergebnisse und verbessert gleichzeitig die Lesbarkeit.

Ja, davon könnte man ausgehen. Wenn Sie die Variable i als flüchtig deklarieren, teilen Sie dem Compiler mit, auf i nicht zu optimieren.

Das stimmt meiner Meinung nach nicht ganz.
iLeider sagt dies dem Compiler nicht nur etwas über die Optimierung, sondern zwingt einen auch dazu, die Variable im RAM zuzuweisen, sodass mein Code erheblich größer und langsamer wird. In meinem Fall ist jeder CPU-Tick wert, daher möchte ich einen Weg finden, die Optimierung möglichst inline zu steuern.
@VladimirCravero, was meinst du damit? Könnten Sie klarer machen?
Was ich meinte, ist, dass ich mir nicht so sicher wäre, was ein Compiler tut. Das Deklarieren einer Variablen als flüchtig teilt dem Compiler mit, dass sie sich woanders ändern könnte, also sollte es das wirklich tun.
Dies ist eine richtige Antwort. Beachten Sie, dass das iEinfügen eines Registers bereits eine Optimierung ist, daher ist der erste Kommentar von @Roman ein Missverständnis.
@ Roman Matveev register unsigned char volatile i __asm__("r1");vielleicht?
Das Deklarieren ials flüchtig löst alles. Dies wird durch den C-Standard 5.1.2.3 garantiert. Ein konformer Compiler darf diese Schleifen nicht wegoptimieren, wenn sie iflüchtig ist. Glücklicherweise ist GCC ein konformer Compiler. Leider gibt es viele Möchtegern-C-Compiler, die nicht dem Standard entsprechen, aber das ist für diese spezielle Frage irrelevant. Es ist absolut kein Inline-Assembler erforderlich.

Nach der ersten Schleife iist eine Konstante. Die Initialisierung von iund der Schleife bewirkt nichts anderes, als einen konstanten Wert zu erzeugen. Nichts im Standard legt fest, dass diese Schleife unverändert kompiliert werden muss. Auch über das Timing sagt die Norm nichts aus. Der kompilierte Code muss sich so verhalten, als wäre die Schleife vorhanden, und dies ist der Fall. Sie können nicht zuverlässig sagen, dass diese Optimierung unter dem Standard durchgeführt wurde (Timing zählt nicht).

Die zweite Schleife sollte ebenfalls gelöscht werden. Ich halte es für einen Fehler (oder eine fehlende Optimierung), was es nicht ist. Nach der Schleife iist konstant Null. Der Code sollte durch Setzen iauf Null ersetzt werden.

Ich denke GCC hält irein aus dem Grund rum, dass man einen (undurchsichtigen) Portzugriff beeinflussen könnte i.

Verwenden

asm volatile ("nop");

um GCC dazu zu bringen zu glauben, dass die Schleife etwas tut.